Skip to content

Commit

Permalink
Allow for optional PyApp variables (#20)
Browse files Browse the repository at this point in the history
* Add additional pyapp variables as prompted question

* add optional pyapp vars as a command line option for quiet initialization

* set the actual environment
  • Loading branch information
trappitsch authored Mar 4, 2024
1 parent 849c7d1 commit ac1d5d3
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 6 deletions.
14 changes: 12 additions & 2 deletions src/box/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,22 @@ def cli():
@click.option(
"-opt", "--optional-deps", help="Set optional dependencies for the project."
)
@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(
"-py",
"--python-version",
type=click.Choice(ut.PYAPP_PYTHON_VERSIONS),
help="Set the python version to use with PyApp.",
)
def init(quiet, builder, optional_deps, entry, python_version):
def init(quiet, builder, optional_deps, entry, python_version, opt_pyapp_vars):
"""Initialize a new project in the current folder."""
ut.check_pyproject()
my_init = InitializeProject(
Expand All @@ -50,6 +58,7 @@ def init(quiet, builder, optional_deps, entry, python_version):
optional_deps=optional_deps,
app_entry=entry,
python_version=python_version,
opt_pyapp_vars=opt_pyapp_vars,
)
my_init.initialize()

Expand Down Expand Up @@ -87,7 +96,8 @@ def package(verbose, pyapp_source):
binary_file = my_packager.binary_name
fmt.success(
f"Project successfully packaged.\n"
f"You can find the executable file {binary_file.name} in the `target/release` folder."
f"You can find the executable file {binary_file.name} "
f"in the `target/release` folder."
)


Expand Down
8 changes: 8 additions & 0 deletions src/box/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ 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) -> Dict:
"""Return [project.gui-scripts] or [project.scripts] entry if available.
Expand Down
31 changes: 31 additions & 0 deletions src/box/initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def __init__(
optional_deps: str = None,
app_entry: str = None,
python_version: str = None,
opt_pyapp_vars: str = None,
):
"""Initialize the InitializeProject class.
Expand All @@ -31,10 +32,12 @@ def __init__(
:param optional_deps: Optional dependencies for the project.
:param app_entry: App entry for the project.
:param python_version: Python version for the project.
:param opt_pyapp_vars: Optional PyApp variables to set.
"""
self._quiet = quiet
self._builder = builder
self._optional_deps = optional_deps
self._opt_paypp_vars = opt_pyapp_vars
self._app_entry = app_entry
self._python_version = python_version

Expand All @@ -52,6 +55,7 @@ def initialize(self):
self._set_optional_deps()
self._set_app_entry()
self._set_python_version()
self._set_optional_pyapp_variables()

if not self._quiet:
fmt.success("Project initialized.")
Expand Down Expand Up @@ -141,6 +145,33 @@ 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."""
opt_vars = None
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="",
)

if opt_vars == "":
opt_vars = None

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:
Expand Down
3 changes: 3 additions & 0 deletions src/box/packager.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ def _set_env(self):
os.environ["PYAPP_PYTHON_VERSION"] = py_version
if value := self.config.optional_dependencies:
os.environ["PYAPP_PIP_OPTIONAL_DEPS"] = value
optional_pyapp_vars = self.config.optional_pyapp_variables
for key, value in optional_pyapp_vars.items():
os.environ[key] = value

# STATIC METHODS #
@staticmethod
Expand Down
37 changes: 34 additions & 3 deletions tests/cli/test_cli_initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import box.utils as ut


@pytest.mark.parametrize("app_entry", ["\n\nhello\n3.8", "\ngui\n127\nhello\n"])
@pytest.mark.parametrize(
"app_entry", ["\n\nhello\n3.8\nPYAPP_FULL_ISOLATION 1", "\ngui\n127\nhello\n\n"]
)
def test_initialize_project_app_entry_typed(rye_project_no_box, app_entry):
"""Initialize a new project."""
# modify pyproject.toml to contain an app entry
Expand All @@ -33,7 +35,7 @@ def test_initialize_project_app_entry_typed(rye_project_no_box, app_entry):

# assert name is in pyproject.toml
pyproj = PyProjectParser()
app_entry_exp = app_entry.split("\n")[-2]
app_entry_exp = app_entry.split("\n")[-3]
assert pyproj.app_entry == app_entry_exp

# assert that extra dependencies were set
Expand All @@ -44,11 +46,22 @@ def test_initialize_project_app_entry_typed(rye_project_no_box, app_entry):
assert pyproj.builder == "rye"

# assert default python version
py_version_exp = app_entry.split("\n")[-1]
py_version_exp = app_entry.split("\n")[-2]
if py_version_exp == "":
py_version_exp = ut.PYAPP_PYTHON_VERSIONS[-1]
assert pyproj.python_version == py_version_exp

# optional pyapp variables
optional_pyapp_vars_exp = app_entry.split("\n")[-1]
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


def test_initialize_with_options(rye_project_no_box):
"""Initialize a new project with options."""
Expand All @@ -70,6 +83,8 @@ def test_initialize_with_options(rye_project_no_box):
optional_deps,
"-b",
builder,
"--opt-pyapp-vars",
"PYAPP_FULL_ISOLATION 1",
],
)

Expand All @@ -85,6 +100,7 @@ def test_initialize_with_options(rye_project_no_box):
assert pyproj.python_version == py_version
assert pyproj.optional_dependencies == optional_deps
assert pyproj.app_entry == entry_point
assert pyproj.optional_pyapp_variables == {"PYAPP_FULL_ISOLATION": "1"}


def test_initialize_project_quiet(rye_project_no_box):
Expand Down Expand Up @@ -113,6 +129,21 @@ 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\nhello\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()
Expand Down
9 changes: 8 additions & 1 deletion tests/func/test_packager.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ def test_package_pyapp_cargo_and_move(rye_project, mocker, binary_extensions):


@pytest.mark.parametrize("opt_deps", ["gui", None])
def test_set_env(rye_project, opt_deps, mocker):
@pytest.mark.parametrize("opt_pyapp_vars", ["PYAPP_SOMETHING 2", None])
def test_set_env(rye_project, opt_deps, opt_pyapp_vars, mocker):
"""Set environment for `PyApp` packaging."""
config = PyProjectParser()
exec_spec = config.app_entry
Expand All @@ -245,6 +246,10 @@ def test_set_env(rye_project, opt_deps, mocker):
# write optional deps to the pyproject.toml
if opt_deps:
pyproject_writer("optional_deps", opt_deps)
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)

packager = PackageApp()
packager.build()
Expand All @@ -258,6 +263,8 @@ def test_set_env(rye_project, opt_deps, mocker):
assert os.environ["PYAPP_PYTHON_VERSION"] == ut.PYAPP_PYTHON_VERSIONS[-1]
if opt_deps:
assert os.environ["PYAPP_PIP_OPTIONAL_DEPS"] == opt_deps
if opt_pyapp_vars:
assert os.environ["PYAPP_SOMETHING"] == "2"


def test_set_env_delete_existing(rye_project, mocker):
Expand Down

0 comments on commit ac1d5d3

Please sign in to comment.