From 0d63d91d39cf68e159c9337506b8e31b736c33fb Mon Sep 17 00:00:00 2001 From: Reto Trappitsch Date: Fri, 30 Aug 2024 14:02:35 +0200 Subject: [PATCH] Environment variable management (#53) * pyapp variables: remove from init, move in toml to `env-vars` section * Allow setting of string, int, and bool variables * Add possibility to get a variable * BF in uninitialize * Unset variables, list variables * rename function about pyapp variablle -> it's for all env vars * add tests for case where unsetting a non-existing variable --- docs/changelog.md | 17 +++ docs/examples.md | 1 - docs/guide.md | 45 ++++++- src/box/cli.py | 57 ++++++-- src/box/config.py | 61 +++++++-- src/box/env_vars.py | 137 ++++++++++++++++++++ src/box/initialization.py | 35 ----- src/box/packager.py | 2 +- tests/cli/test_cli_env.py | 187 +++++++++++++++++++++++++++ tests/cli/test_cli_initialization.py | 37 +----- tests/cli/test_cli_uninitialize.py | 9 ++ tests/unit/test_config.py | 29 +++++ tests/unit/test_initialization.py | 2 +- tests/unit/test_packager.py | 2 +- 14 files changed, 525 insertions(+), 96 deletions(-) create mode 100644 src/box/env_vars.py create mode 100644 tests/cli/test_cli_env.py diff --git a/docs/changelog.md b/docs/changelog.md index 6225a83..577c3e3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,20 @@ +## v0.4.0 + +**If you are setting PyApp variables, this is a breaking change!** + +- Move environmental variables to their own table in `pyproject.toml`. They are now in `[tool.box.env-vars]`. +- Remove PyApp Variables from initialization and put them into their own command. +- Add a new command `box env` in order to manage environmental variables. + - Add `box env --set KEY=VALUE` to add string variables. + - Add `box env --set-int KEY=VALUE` to add integer variables. + - Add `box env --set-bool KEY=VALUE` to add boolean variables. + - Add `box env --get VAR_NAME` to get the value of a variable. + - Add `box env --unset VAR_NAME` to remove a variable. + - Add `box env --list` to list all variables. +- Bug fix for `box uninit`: Will throw a useful error if not in a `box` project. + +If this breaks your project, you can either run `box uninit` followed by `box init` and re-enter the variables, or you can manually edit the `pyproject.toml` file. + ## v0.3.0 - Fix linux GUI uninstaller, such that it will only delete the installation folder if it is empty. diff --git a/docs/examples.md b/docs/examples.md index 9335f59..a8e0d5c 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -26,7 +26,6 @@ a simple PyQt6 wrapper around 4. Provide the app entry point: The automatically selected point is correct, so we choose from the list - option 0. 5. Choose the App entry type according to PyApp's options: Here we use `spec`. 6. Provide the Python version to package with: Here we use the default `3.12`. - 7. Provide additonal PyApp variables: Not required here (default). 2. After successfull initialization, we package the project with `box package`. This first builds the package and then packages it with PyApp. Hold on... 3. The executable is now in `target/release`. 4. Run the executable! diff --git a/docs/guide.md b/docs/guide.md index b91fc3e..2d76d59 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -27,7 +27,6 @@ The following table shows a list of questions, their default values, their argum | Please type an app entry for the project or choose one from the list below | First entry in `pyproject.toml` for `[project.gui-scripts]`, if not available, then for `[project.scripts]`. If no entry points are given, the default value is set to `package_name:run`. | `-e`
`--entry` | *Required:* The entry point for the application. This is the command that will be used to start the application. If you have a `pyproject.toml` file, `box` will try and read potential entry points that you can select by passing it the digit of the list. You can also provide an entry point manually by typing it here. | | Choose an entry type for the project. | `spec` | `-et`
`--entry-type` | *Required:* This specifies the type of the entry point that `PyApp` will use. `spec` (default) is an object reference, `module` for a python module, `script` for a python script, and `notebook` for a jupyter notebook. Details can be found [here](https://ofek.dev/pyapp/latest/config/#execution-mode). | | Choose a python version to package the project with. | `3.12` | `--py`
`--python-version` | *Required:* The python version to package with your project. You can choose any python version from the list. More details can be found on the `PyApp` website [here](https://ofek.dev/pyapp/latest/config/#python-distribution). | -| Optional PyApp variables | `None` | `--opt-pyapp-vars` | *Optional:* Set any optional environmental variables that you can use in `PyApp` here by giving a list of variable name followed by the value for that variable. Note that the total number of arguments must be even. See the [`PyApp` documentation](https://ofek.dev/pyapp) for more information. If there are no optional variables, just hit enter. | If you provided all the answers, your project should have been successfully initialized and print an according message. @@ -41,6 +40,50 @@ If you provided all the answers, your project should have been successfully init If you re-initalize a project, `box` will use the already set values as proposed default values. If you re-initialize in quiet mode and just give one new option, all other options will stay the same. +## Manage environmental variables + +PyApp uses environmental variables for all configurations. +While `box` includes the basics of PyApp configuration, +you might want to set additional environmental variables. +This is done with the `box env` command. + +### Set an environmental variable + +You can set three types of environmental variables: + +- `--set KEY=VALUE` to set a string variable. +- `--set-int KEY=VALUE` to set an integer variable. +- `--set-bool KEY=VALUE` to set a boolean variable. + +For example, to set a string variable `MY_VAR` to `my_value`, type: + +``` +box env --set MY_VAR=my_value +``` + +### Get an environmental variable + +Once set, you can simply get an environmental variable by typing: + +``` +box env --get VARIABLE_NAME +``` + +If not variable with this name is defined, a warning will be printed. +To list all currently set variables, type: + +``` +box env --list +``` + +### Unset a variable + +To unset a variable, type: + +``` +box env --unset VARIABLE_NAME +``` + ## Packaging To package your project, simply run: diff --git a/src/box/cli.py b/src/box/cli.py index f7b002c..f15bc29 100644 --- a/src/box/cli.py +++ b/src/box/cli.py @@ -5,6 +5,7 @@ import rich_click as click import box +from box import env_vars from box.cleaner import CleanProject from box.config import uninitialize from box.initialization import InitializeProject @@ -46,14 +47,6 @@ def cli(): default=None, help="Set the project as a GUI project. In quiet mode, this will default to `False`.", ) -@click.option( - "--opt-pyapp-vars", - help=( - "Set optional PyApp variables for the project. " - "`Example: PYAPP_FULL_ISOLATION 1`" - ), - type=str, -) @click.option("-e", "--entry", help="Set the app entry for the project.") @click.option( "-et", @@ -77,7 +70,6 @@ def init( entry, entry_type, python_version, - opt_pyapp_vars, ): """Initialize a new project in the current folder.""" ut.check_pyproject() @@ -89,11 +81,54 @@ def init( app_entry=entry, app_entry_type=entry_type, python_version=python_version, - opt_pyapp_vars=opt_pyapp_vars, ) my_init.initialize() +@cli.command(name="env") +@click.option( + "--get", "get_var", help="Get the value that is currently set to a variable." +) +@click.option("--list", "list_vars", is_flag=True, help="List all variables set.") +@click.option( + "--set", + "set_string", + help=("Set a `key=value` environmental variable pair with a string value."), +) +@click.option( + "--set-bool", + help=( + "Set a `key=value` environmental variable pair with a boolean. " + "Valid boolean values are `0`, `1`, `True`, `False` (case insensitive)." + ), +) +@click.option( + "--set-int", + help=("Set a `key=value` environmental variable pair with an integer value."), +) +@click.option("--unset", help="Unset variable with a given name.") +def env(get_var, list_vars, set_bool, set_int, set_string, unset): + """Manage the environmental variables. + + All environmental variables will be set when packaging the app with PyApp. + Therefore, if you want to set specific PYAPP_X variables, set them here. + """ + ut.check_boxproject() + + if get_var: + env_vars.get_var(get_var) + if set_bool: + env_vars.set_bool(set_bool) + if set_int: + env_vars.set_int(set_int) + if set_string: + env_vars.set_string(set_string) + if unset: + env_vars.unset(unset) + if list_vars: + env_vars.get_list() + + @cli.command(name="package") @click.option( "-v", @@ -236,7 +271,7 @@ def uninit(clean_project): All references to `box` will be removed from the `pyproject.toml` file. """ - ut.check_pyproject() + ut.check_boxproject() if clean_project: clean() uninitialize() diff --git a/src/box/config.py b/src/box/config.py index 2e04a8a..704835a 100644 --- a/src/box/config.py +++ b/src/box/config.py @@ -40,6 +40,14 @@ def builder(self) -> str: """Return the builder of the project.""" return self._pyproject["tool"]["box"]["builder"] + @property + def env_vars(self) -> Dict: + """Return optional pyapp variables as list (if set), otherwise empty dict.""" + try: + return self._pyproject["tool"]["box"]["env-vars"] + except (KeyError, TypeError): + return dict() + @property def is_box_project(self): """Return if this folder is a box project or not.""" @@ -72,14 +80,6 @@ def optional_dependencies(self) -> Union[str, None]: except KeyError: return None - @property - def optional_pyapp_variables(self) -> Dict: - """Return optional pyapp variables as list (if set), otherwise empty dict.""" - try: - return self._pyproject["tool"]["box"]["optional_pyapp_vars"] - except KeyError: - return {} - @property def possible_app_entries(self) -> OrderedDict: """Return [project.gui-scripts] or [project.scripts] entry if available. @@ -124,12 +124,17 @@ def version(self) -> str: return self._project["version"] -def pyproject_writer(key: str, value: Any) -> None: +def pyproject_writer(key: str, value: Any, category: str = None) -> None: """Modify the existing `pyproject.toml` file using `tomlkit`. Project specific, the table [tools.box] is used. If the table does not exist, it is created. If the key does not exist, it is created. If the key exists, it is overwritten. + + :param key: Key to write to. + :param value: Value to write to key. + :param category: If given, will write to ["tool"]["box"]["category"]["key"]["value"] + """ pyproject_file = Path("pyproject.toml") if not pyproject_file.is_file(): @@ -161,7 +166,22 @@ def pyproject_writer(key: str, value: Any) -> None: tool_table.append("box", box_table) doc.add("tool", tool_table) - box_table.update({key: value}) + if not category: + edit_table = box_table + else: + try: + edit_table = doc["tool"]["box"][category] + except KeyError: + doc.add(tomlkit.nl()) + tool_table = tomlkit.table(True) + category_table = tomlkit.table(True) + tool_table.append("box", category_table) + category_table.add(tomlkit.nl()) + edit_table = tomlkit.table() + category_table.append(category, edit_table) + doc.add("tool", tool_table) + + edit_table.update({key: value}) with open(pyproject_file, "w", newline="\n") as f: tomlkit.dump(doc, f) @@ -178,3 +198,24 @@ def uninitialize() -> None: with open(pyproject_file, "w", newline="\n") as f: tomlkit.dump(doc, f) + + +def unset_env_variable(var_name: str) -> bool: + """Unset a variable name and return status if done or not. + + :param var_name: Variable name in ["tool.box.env-vars"] + + :return: True if variable successfully unset, False if not found, otherwise. + """ + pyproject_file = Path("pyproject.toml") + + with open(pyproject_file, "rb") as f: + doc = tomlkit.load(f) + + try: + doc["tool"]["box"]["env-vars"].remove(var_name) + with open(pyproject_file, "w", newline="\n") as f: + tomlkit.dump(doc, f) + return True + except: # noqa: E722 + return False diff --git a/src/box/env_vars.py b/src/box/env_vars.py new file mode 100644 index 0000000..b29a344 --- /dev/null +++ b/src/box/env_vars.py @@ -0,0 +1,137 @@ +# Deal with setting and getting environmental variables. Getting all at once is handled in the `config.py` + +from enum import Enum + +import rich_click as click +from rich_click import ClickException + +import box.config as cfg +import box.formatters as fmt + + +class VariableType(Enum): + """Define valid variable types that can be set.""" + + STRING = "string" # default + INT = "int" + BOOL = "bool" + + +def get_list() -> None: + """Get a list of all environmental variables set in the configuration.""" + parser = cfg.PyProjectParser() + env_vars = parser.env_vars + + if not env_vars: + fmt.warning("No variables set.") + else: + for key, value in env_vars.items(): + click.secho(f"{key}: {value}") + + +def get_var(name: str) -> None: + """Get a variable name and display its name.""" + parser = cfg.PyProjectParser() + env_vars = parser.env_vars + + try: + click.secho(env_vars[name]) + except KeyError: + fmt.warning(f"No variable named {name} found in the configuration.") + + +def set_bool(key_val: str) -> None: + """Set a key-value pair as a boolean. + + :param key_val: Key-value pair to set environmental variable to, as an boolean. + """ + typ = VariableType.BOOL + _set_variable(key_val, typ) + + +def set_int(key_val: str) -> None: + """Set a key-value pair as a integer. + + :param key_val: Key-value pair to set environmental variable to, as an integer. + """ + typ = VariableType.INT + _set_variable(key_val, typ) + + +def set_string(key_val: str) -> None: + """Set a key-value pair as a string. + + :param key_val: Key-value pair to set environmental variable to, as a string. + """ + typ = VariableType.STRING + _set_variable(key_val, typ) + + +def unset(var: str) -> None: + """Unset a variable. + + If set, will print a success message, otherwise a warning that variable + could not be found. + + :param var: Variable name. + """ + status = cfg.unset_env_variable(var) + if status: + fmt.success(f"Variable {var} unset.") + else: + fmt.warning(f"Could not find variable {var}.") + + +def _check_bool(val: str) -> bool: + """Check an input string to see if it contains a boolean value. + + Valid strings are: + - True: "1", "True" (case insensitive) + - False: "0", "False" (case insensitive) + + :param val: Value as a string. + + :return: Boolean value of the input string. + + :raises: ClickException if none of these values were given. + """ + if val == "1" or val.lower() == "true": + return True + elif val == "0" or val.lower() == "false": + return False + else: + raise ClickException(f"Cannot convert {val} to a boolean.") + + +def _set_variable(key_val: str, typ: VariableType) -> None: + """Set a key-value pair with a given type for the type. + + If the key-value pair is invalid, the CLI will return an error saying so. + Otherwise, the variable will be written to `pyproject.toml`. + Already existing values will simply be overwritten. + + :param key_val: Key-value pair to set. + :param typ: Type to set it too. + """ + key_val = key_val.split("=") + if len(key_val) != 2: + raise ClickException("Variables to set must be a key-value pair") + + if typ == VariableType.STRING: + key = key_val[0] + value = key_val[1] + elif typ == VariableType.INT: + key = key_val[0] + try: + value = int(key_val[1]) + except Exception as e: + raise ClickException( + f"Problem converting {key_val[1]} to an integer" + ) from e + elif typ == VariableType.BOOL: + key = key_val[0] + value = _check_bool(key_val[1]) + + cfg.pyproject_writer(key, value, category="env-vars") + + fmt.success(f"Variable {key} successfully set to {value} (type {typ.value}).") diff --git a/src/box/initialization.py b/src/box/initialization.py index 314620c..2c75961 100644 --- a/src/box/initialization.py +++ b/src/box/initialization.py @@ -60,7 +60,6 @@ def initialize(self): self._set_app_entry() self._set_app_entry_type() self._set_python_version() - self._set_optional_pyapp_variables() if not self._quiet: fmt.success("Project initialized.") @@ -199,40 +198,6 @@ def _set_optional_deps(self): if opt_deps != "": pyproject_writer("optional_deps", opt_deps) - def _set_optional_pyapp_variables(self): - """Set optional environmental variables for PyApp.""" - default_opt_vars = "" - - tmp = self.pyproj.optional_pyapp_variables - for key, value in tmp.items(): - default_opt_vars += f"{key} {value} " - - if self._opt_paypp_vars: - opt_vars = self._opt_paypp_vars - elif not self._quiet: - opt_vars = click.prompt( - "Please enter optional PyApp variables to set. " - "Example: `PYAPP_SKIP_INSTALL 1 PYAPP_FULL_ISOLATION 1", - type=str, - default=default_opt_vars, - ) - - if opt_vars == "": - opt_vars = None - else: - opt_vars = None if default_opt_vars == "" else default_opt_vars - - if opt_vars: - opt_vars = opt_vars.split() - if len(opt_vars) % 2 != 0: - fmt.warning("Invalid number of variables. Please try again.") - self._set_optional_pyapp_variables() - else: - opt_vars_dict = {} - for i in range(0, len(opt_vars), 2): - opt_vars_dict[opt_vars[i]] = opt_vars[i + 1] - pyproject_writer("optional_pyapp_vars", opt_vars_dict) - def _set_pyproj(self): """Check if the pyproject.toml file is valid.""" try: diff --git a/src/box/packager.py b/src/box/packager.py index 03e00a8..4060167 100644 --- a/src/box/packager.py +++ b/src/box/packager.py @@ -277,7 +277,7 @@ def _set_env(self): os.environ["PYAPP_PYTHON_VERSION"] = py_version if value := self.config.optional_dependencies: os.environ["PYAPP_PROJECT_FEATURES"] = value - optional_pyapp_vars = self.config.optional_pyapp_variables + optional_pyapp_vars = self.config.env_vars for key, value in optional_pyapp_vars.items(): os.environ[key] = value if self.config.is_gui: diff --git a/tests/cli/test_cli_env.py b/tests/cli/test_cli_env.py new file mode 100644 index 0000000..03ce600 --- /dev/null +++ b/tests/cli/test_cli_env.py @@ -0,0 +1,187 @@ +# Tests for setting environment variables with `box env ...` + +from click.testing import CliRunner +import pytest + +from box.cli import cli + + +def test_env_not_box_project(rye_project_no_box): + """Ensure we check first if this is a box project.""" + runner = CliRunner() + result = runner.invoke(cli, ["env", "--set", "MY_VAR=3"]) + + assert result.exit_code != 0 + assert "not a box project" in result.output + + +@pytest.mark.parametrize("vars", [["PYAPP_SOMETHING", 1], ["TEST_ENV", "qerty"]]) +def test_env_set(rye_project, vars): + """Set some environments in the box project and ensure they are there.""" + var = vars[0] + value = vars[1] + + runner = CliRunner() + result = runner.invoke(cli, ["env", "--set", f"{var}={value}"]) + + assert result.exit_code == 0 + + with open("pyproject.toml") as fl: + data = fl.read() + assert "[tool.box.env-vars]" in data + assert f'{var} = "{value}"' in data + + assert var in result.output + assert f"{value}" in result.output + assert "type string" in result.output + + +@pytest.mark.parametrize("key_val", ["key=value=something", "only_a_key"]) +def test_env_set_key_value_invalid(rye_project, key_val): + """Ensure an error is raised if key-value pair to be set is invalid.""" + runner = CliRunner() + result = runner.invoke(cli, ["env", "--set", key_val]) + + assert result.exit_code != 0 + assert "Variables to set must be a key-value pair" in result.output + + +def test_env_set_int(rye_project): + """Set an integer environments in the box project and ensure they it's there.""" + var = "MY_VARIABLE" + value = 42 + + runner = CliRunner() + result = runner.invoke(cli, ["env", "--set-int", f"{var}={value}"]) + + assert result.exit_code == 0 + + with open("pyproject.toml") as fl: + data = fl.read() + assert "[tool.box.env-vars]" in data + assert f"{var} = {value}" in data + + assert var in result.output + assert f" {value} " in result.output + assert "type int" in result.output + + +def test_env_set_int_convertion_error(rye_project): + """Raise conversion error if int variable setting with invalid value.""" + var = "MY_VARIABLE" + value = "b3sdf" + + runner = CliRunner() + result = runner.invoke(cli, ["env", "--set-int", f"{var}={value}"]) + + assert result.exit_code != 0 + assert "Problem converting" in result.output + assert value in result.output + + +@pytest.mark.parametrize( + "value_bool", + [ + ["0", False], + ["1", True], + ["False", False], + ["True", True], + ["falSE", False], + ["true", True], + ], +) +def test_env_set_bool(rye_project, value_bool): + """Set some bool environments in the box project and ensure they are there.""" + var = "MY_VARIABLE" + value, vbool = value_bool + + runner = CliRunner() + result = runner.invoke(cli, ["env", "--set-bool", f"{var}={value}"]) + + assert result.exit_code == 0 + + with open("pyproject.toml") as fl: + data = fl.read() + assert "[tool.box.env-vars]" in data + assert f"{var} = {str(vbool).lower()}" in data + + assert var in result.output + assert f"{vbool}" in result.output + assert "type bool" in result.output + + +def test_env_set_bool_convertion_error(rye_project): + """Raise conversion error if bool variable setting with invalid bool.""" + var = "MY_VARIABLE" + value = "b3sdf a" + + runner = CliRunner() + result = runner.invoke(cli, ["env", "--set-bool", f"{var}={value}"]) + + assert result.exit_code != 0 + assert "Cannot convert" in result.output + assert value in result.output + + +def test_env_get(rye_project): + """First set, then get an environment variable from box project.""" + var = "MY_VARIABLE" + value = "42" + + runner = CliRunner() + runner.invoke(cli, ["env", "--set-int", f"{var}={value}"]) + result = runner.invoke(cli, ["env", "--get", var]) + + assert result.exit_code == 0 + assert value in result.output + assert var not in result.output + + +def test_env_list(rye_project): + """List all environment variables set in the box project.""" + vars = [["PYAPP_SOMETHING", 1], ["TEST_ENV", "qerty"]] + + runner = CliRunner() + + result_none = runner.invoke(cli, ["env", "--list"]) + + for var, value in vars: + runner.invoke(cli, ["env", "--set", f"{var}={value}"]) + result_some = runner.invoke(cli, ["env", "--list"]) + + assert result_none.exit_code == 0 + assert "No variables set" in result_none.output + + assert result_some.exit_code == 0 + for var, value in vars: + assert var in result_some.output + assert f"{value}" in result_some.output + + +def test_env_get_na(rye_project): + """If no variable with given name is present, print a warning.""" + runner = CliRunner() + result = runner.invoke(cli, "env --get SOME_VARIABLE") + + assert result.exit_code == 0 + assert "Warning:" in result.output + + +def test_env_unset(rye_project): + """Unset an already set variable.""" + var = "MY_VAR" + val = 42 + + runner = CliRunner() + + runner.invoke(cli, ["env", "--set-int", f"{var}={val}"]) + result_1 = runner.invoke(cli, ["env", "--get", var]) + runner.invoke(cli, ["env", "--unset", var]) + result_2 = runner.invoke(cli, ["env", "--get", var]) + + result_3 = runner.invoke(cli, ["env", "--unset", var]) + + assert "Warning" not in result_1.output + assert "Warning" in result_2.output + + assert "Warning" in result_3.output diff --git a/tests/cli/test_cli_initialization.py b/tests/cli/test_cli_initialization.py index aa2dced..59fc895 100644 --- a/tests/cli/test_cli_initialization.py +++ b/tests/cli/test_cli_initialization.py @@ -11,7 +11,7 @@ @pytest.mark.parametrize( "app_entry", - ["\n\n\nhello\n\n3.8\nPYAPP_FULL_ISOLATION 1", "\ngui\n\n127\nhello\nmodule\n\n"], + ["\n\n\nhello\n\n3.8\n", "\ngui\n\n127\nhello\nmodule\n\n"], ) def test_initialize_project_app_entry_typed(rye_project_no_box, app_entry): """Initialize a new project.""" @@ -21,7 +21,6 @@ def test_initialize_project_app_entry_typed(rye_project_no_box, app_entry): app_entry_exp = app_entry_split[-4] entry_type_exp = app_entry_split[-3] py_version_exp = app_entry_split[-2] - optional_pyapp_vars_exp = app_entry_split[-1] # modify pyproject.toml to contain an app entry with open("pyproject.toml") as fin: @@ -67,16 +66,6 @@ def test_initialize_project_app_entry_typed(rye_project_no_box, app_entry): py_version_exp = ut.PYAPP_PYTHON_VERSIONS[-1] assert pyproj.python_version == py_version_exp - # optional pyapp variables - if optional_pyapp_vars_exp == "": - exp_dict = {} - else: - tmp_split = optional_pyapp_vars_exp.split() - exp_dict = {} - for it in range(0, len(tmp_split), 2): - exp_dict[tmp_split[it]] = tmp_split[it + 1] - assert pyproj.optional_pyapp_variables == exp_dict - @pytest.mark.parametrize("gui", [True, False]) def test_initialize_with_options(rye_project_no_box, gui): @@ -99,8 +88,6 @@ def test_initialize_with_options(rye_project_no_box, gui): optional_deps, "-b", builder, - "--opt-pyapp-vars", - "PYAPP_FULL_ISOLATION 1", ] if gui: args.append("--gui") @@ -122,11 +109,9 @@ def test_initialize_with_options(rye_project_no_box, gui): assert pyproj.is_gui == gui assert pyproj.app_entry == entry_point assert pyproj.app_entry_type == entry_type - assert pyproj.optional_pyapp_variables == {"PYAPP_FULL_ISOLATION": "1"} -@pytest.mark.parametrize("pyapp_extra_vars", ["PYAPP_FULL_ISOLATION 1", None]) -def test_initialize_project_again(rye_project_no_box, pyapp_extra_vars): +def test_initialize_project_again(rye_project_no_box): """Initialization of a previous project sets defaults from previous config.""" builder = "build" entry_point = "myapp:entry" @@ -148,9 +133,6 @@ def test_initialize_project_again(rye_project_no_box, pyapp_extra_vars): builder, "--gui", ] - if pyapp_extra_vars: - args.append("--opt-pyapp-vars") - args.append(pyapp_extra_vars) runner = CliRunner() runner.invoke( @@ -196,21 +178,6 @@ def test_initialize_project_builders(rye_project_no_box, builder): assert pyproj.builder == builder -def test_initialize_project_wrong_number_of_pyapp_vars(rye_project_no_box): - """Initialize a new project with a specific builder.""" - runner = CliRunner() - result = runner.invoke( - cli, - ["init"], - input="\n\n\nhello\n\n\nPYAPP_FULL_ISOLATION 1 2\nPYAPP_FULL_ISOLATION 1", - ) - assert result.exit_code == 0 - - pyproj = PyProjectParser() - exp_dict = {"PYAPP_FULL_ISOLATION": "1"} - assert pyproj.optional_pyapp_variables == exp_dict - - def test_initialize_project_quiet_no_project_script(rye_project_no_box): """Initialize a new project quietly with app_entry as the package name.""" runner = CliRunner() diff --git a/tests/cli/test_cli_uninitialize.py b/tests/cli/test_cli_uninitialize.py index 767b5f4..0731b6b 100644 --- a/tests/cli/test_cli_uninitialize.py +++ b/tests/cli/test_cli_uninitialize.py @@ -19,6 +19,15 @@ def test_uninitialize(rye_project): assert not pyproj.is_box_project +def test_uninintialize_not_box(rye_project_no_box): + """Assure uninit checks if box project.""" + runner = CliRunner() + result = runner.invoke(cli, ["uninit"]) + + assert result.exit_code != 0 + assert "not a box project" in result.output + + def test_uninitialize_clean(rye_project, mocker): """Assure a full clean is called if option `-c` is given.""" clean_mock = mocker.patch("box.cli.clean") diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 7ad06c3..7e88f2f 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -70,6 +70,35 @@ def test_pyproject_parser_rye_project(rye_project): assert isinstance(parser.is_gui, bool) +def test_pyproject_parser_env_vars(rye_project): + """Ensure that environment variables can get gotten after being set.""" + env_vars = {"MY_INTVAR": 0, "MY_STRVAR": "asdf"} + with open("pyproject.toml") as fl: + toml = fl.read() + toml += """ + +[tool.box.env-vars] +""" + for key, value in env_vars.items(): + if isinstance(value, str): + value = f'"{value}"' + toml += f"{key} = {value}\n" + + with open("pyproject.toml", "w") as fl: + fl.write(toml) + fl.flush() + + parser = PyProjectParser() + assert parser.env_vars == env_vars + + +def test_pyproject_parser_env_vars_always_returns_dict(rye_project): + """Ensure that the environment variable parser returns empty dict if key not there.""" + parser = PyProjectParser() + ret_val = parser.env_vars + assert ret_val == {} + + def test_pyproject_writer_set_entry(tmp_path_chdir): """Set the app entry in the pyproject.toml file.""" fname = "pyproject.toml" diff --git a/tests/unit/test_initialization.py b/tests/unit/test_initialization.py index 52c5b3d..403f4b0 100644 --- a/tests/unit/test_initialization.py +++ b/tests/unit/test_initialization.py @@ -20,4 +20,4 @@ def test_quiet_init(rye_project_no_box): assert pyproj.app_entry == f"{rye_project_no_box.name}:run" assert pyproj.app_entry_type == "spec" assert pyproj.python_version == "3.12" - assert pyproj.optional_pyapp_variables == {} + assert pyproj.env_vars == {} diff --git a/tests/unit/test_packager.py b/tests/unit/test_packager.py index f6c44ba..ab8f886 100644 --- a/tests/unit/test_packager.py +++ b/tests/unit/test_packager.py @@ -309,7 +309,7 @@ def test_set_env(rye_project, mocker, app_entry_type, opt_deps, opt_pyapp_vars, if opt_pyapp_vars: tmp_split = opt_pyapp_vars.split() opt_pyapp_vars = {tmp_split[0]: tmp_split[1]} - pyproject_writer("optional_pyapp_vars", opt_pyapp_vars) + pyproject_writer("env-vars", opt_pyapp_vars) pyproject_writer("is_gui", gui) packager = PackageApp()