Skip to content

Commit

Permalink
Add GUI support (#28)
Browse files Browse the repository at this point in the history
* Add `is_gui` to initialization questions/options

* set environment for PyApp packaging correctly

* update docs
  • Loading branch information
trappitsch authored Mar 25, 2024
1 parent 68f4513 commit d74c8b0
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 43 deletions.
17 changes: 9 additions & 8 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ If this flag is set, default values or user provided arguments will be used.

The following table shows a list of questions, their default values, their arguments to provide answers in quiet mode, and an explanation:

| Question | Default | Argument | Explanation |
|----------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Choose a builder tool for the project | `rye` | `-b`<br> `--builder` | *Required:* The builder to use to package your pyproject file. Valid tools are `rye`, `hatch`, `pdm`, `build`, and `flit`. Ensure that the builder is available in the environment in which you run box. |
| Provide any optional dependencies for the project. | `None` | `--opt`<br>`--optional-deps` | *Optional:* Set any optional dependencies for the project. These are the dependencies that you would install in square brackets, e.g., via `pip install package_name[optional]`. If there are no optional dependencies, just hit enter. |
| 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`<br>`--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`<br>`--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`<br>`--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. |
| Question | Default | Argument | Explanation |
|----------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Choose a builder tool for the project | `rye` | `-b`<br> `--builder` | *Required:* The builder to use to package your pyproject file. Valid tools are `rye`, `hatch`, `pdm`, `build`, and `flit`. Ensure that the builder is available in the environment in which you run box. |
| Provide any optional dependencies for the project. | `None` | `--opt`<br>`--optional-deps` | *Optional:* Set any optional dependencies for the project. These are the dependencies that you would install in square brackets, e.g., via `pip install package_name[optional]`. If there are no optional dependencies, just hit enter. |
| Is this a GUI project? | `False` | `--gui` flag to toggle to `True` | Packaging a GUI project and a CLI project take place slightly differently in PyApp. If you package a GUI project without setting this option, a console will be shown in Windows and macos along with your GUI. | |
| 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`<br>`--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`<br>`--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`<br>`--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.

Expand Down
16 changes: 15 additions & 1 deletion src/box/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ def cli():
@click.option(
"-opt", "--optional-deps", help="Set optional dependencies for the project."
)
@click.option(
"--gui",
is_flag=True,
default=None,
help="Set the project as a GUI project. In quiet mode, this will default to `False`.",
)
@click.option(
"--opt-pyapp-vars",
help=(
Expand All @@ -58,14 +64,22 @@ def cli():
help="Set the python version to use with PyApp.",
)
def init(
quiet, builder, optional_deps, entry, entry_type, python_version, opt_pyapp_vars
quiet,
builder,
optional_deps,
gui,
entry,
entry_type,
python_version,
opt_pyapp_vars,
):
"""Initialize a new project in the current folder."""
ut.check_pyproject()
my_init = InitializeProject(
quiet=quiet,
builder=builder,
optional_deps=optional_deps,
is_gui=gui,
app_entry=entry,
app_entry_type=entry_type,
python_version=python_version,
Expand Down
5 changes: 5 additions & 0 deletions src/box/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ def is_box_project(self):
except KeyError:
return False

@property
def is_gui(self) -> bool:
"""Return if the project is a GUI project."""
return self._pyproject["tool"]["box"]["is_gui"]

@property
def name(self) -> str:
"""Return the name of the project."""
Expand Down
19 changes: 19 additions & 0 deletions src/box/initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def __init__(
quiet: bool = False,
builder: str = None,
optional_deps: str = None,
is_gui: bool = None,
app_entry: str = None,
app_entry_type: str = None,
python_version: str = None,
Expand All @@ -28,6 +29,7 @@ def __init__(
:param quiet: Flag to suppress output
:param builder: Builder tool to use.
:param optional_deps: Optional dependencies for the project.
:param is_gui: Flag to set the project as a GUI project.
:param app_entry: App entry for the project.
:param app_entry_type: Entry type for the project in PyApp.
:param python_version: Python version for the project.
Expand All @@ -36,6 +38,7 @@ def __init__(
self._quiet = quiet
self._builder = builder
self._optional_deps = optional_deps
self._is_gui = is_gui
self._opt_paypp_vars = opt_pyapp_vars
self._app_entry = app_entry
self._app_entry_type = app_entry_type
Expand All @@ -53,6 +56,7 @@ def initialize(self):
"""
self._set_builder()
self._set_optional_deps()
self._set_is_gui()
self._set_app_entry()
self._set_app_entry_type()
self._set_python_version()
Expand Down Expand Up @@ -164,6 +168,21 @@ def _set_builder(self):
# reload
self._set_pyproj()

def _set_is_gui(self):
"""Set if the project is a GUI project or not."""
if self._is_gui is not None:
is_gui = self._is_gui
else:
if self._quiet:
try:
is_gui = self.pyproj.is_gui
except KeyError:
is_gui = False
else:
is_gui = click.confirm("Is this a GUI project?", default=False)

pyproject_writer("is_gui", is_gui)

def _set_optional_deps(self):
"""Set optional dependencies for the project (if any)."""
if (tmp := self.pyproj.optional_dependencies) is not None:
Expand Down
2 changes: 2 additions & 0 deletions src/box/packager.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ def _set_env(self):
optional_pyapp_vars = self.config.optional_pyapp_variables
for key, value in optional_pyapp_vars.items():
os.environ[key] = value
if self.config.is_gui:
os.environ["PYAPP_IS_GUI"] = "1"

# STATIC METHODS #
@staticmethod
Expand Down
52 changes: 30 additions & 22 deletions tests/cli/test_cli_initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

@pytest.mark.parametrize(
"app_entry",
["\n\nhello\n\n3.8\nPYAPP_FULL_ISOLATION 1", "\ngui\n127\nhello\nmodule\n\n"],
["\n\n\nhello\n\n3.8\nPYAPP_FULL_ISOLATION 1", "\ngui\n\n127\nhello\nmodule\n\n"],
)
def test_initialize_project_app_entry_typed(rye_project_no_box, app_entry):
"""Initialize a new project."""
Expand Down Expand Up @@ -59,6 +59,9 @@ def test_initialize_project_app_entry_typed(rye_project_no_box, app_entry):
# assert that default builder is set to rye
assert pyproj.builder == "rye"

# assert not a gui project
assert not pyproj.is_gui

# assert default python version
if py_version_exp == "":
py_version_exp = ut.PYAPP_PYTHON_VERSIONS[-1]
Expand All @@ -75,33 +78,35 @@ def test_initialize_project_app_entry_typed(rye_project_no_box, app_entry):
assert pyproj.optional_pyapp_variables == exp_dict


def test_initialize_with_options(rye_project_no_box):
@pytest.mark.parametrize("gui", [True, False])
def test_initialize_with_options(rye_project_no_box, gui):
"""Initialize a new project with options."""
py_version = "3.8"
entry_point = "myapp:entry"
entry_type = "module"
optional_deps = "gui"
builder = "hatch"

args = [
"init",
"-e",
entry_point,
"-et",
entry_type,
"-py",
py_version,
"-opt",
optional_deps,
"-b",
builder,
"--opt-pyapp-vars",
"PYAPP_FULL_ISOLATION 1",
]
if gui:
args.append("--gui")

runner = CliRunner()
result = runner.invoke(
cli,
[
"init",
"-e",
entry_point,
"-et",
entry_type,
"-py",
py_version,
"-opt",
optional_deps,
"-b",
builder,
"--opt-pyapp-vars",
"PYAPP_FULL_ISOLATION 1",
],
)
result = runner.invoke(cli, args)

assert result.exit_code == 0
assert "Project initialized." in result.output
Expand All @@ -114,6 +119,7 @@ def test_initialize_with_options(rye_project_no_box):
assert pyproj.builder == builder
assert pyproj.python_version == py_version
assert pyproj.optional_dependencies == optional_deps
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"}
Expand Down Expand Up @@ -145,6 +151,7 @@ def test_initialize_project_again(rye_project_no_box):
builder,
"--opt-pyapp-vars",
pyapp_vars,
"--gui",
],
)

Expand All @@ -155,6 +162,7 @@ def test_initialize_project_again(rye_project_no_box):
assert pyproj.builder == builder
assert pyproj.python_version == py_version
assert pyproj.optional_dependencies == optional_deps
assert pyproj.is_gui
assert pyproj.app_entry == entry_point
assert pyproj.app_entry_type == entry_type

Expand All @@ -177,7 +185,7 @@ def test_initialize_project_quiet(rye_project_no_box):
def test_initialize_project_builders(rye_project_no_box, builder):
"""Initialize a new project with a specific builder."""
runner = CliRunner()
result = runner.invoke(cli, ["init"], input=f"{builder}\n\nsome_entry")
result = runner.invoke(cli, ["init"], input=f"{builder}\n\n\nsome_entry")
assert result.exit_code == 0

# assert that default builder is set to rye
Expand All @@ -191,7 +199,7 @@ def test_initialize_project_wrong_number_of_pyapp_vars(rye_project_no_box):
result = runner.invoke(
cli,
["init"],
input="\n\nhello\n\n\nPYAPP_FULL_ISOLATION 1 2\nPYAPP_FULL_ISOLATION 1",
input="\n\n\nhello\n\n\nPYAPP_FULL_ISOLATION 1 2\nPYAPP_FULL_ISOLATION 1",
)
assert result.exit_code == 0

Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def rye_project(tmp_path):
pyproject_writer("builder", "rye")
pyproject_writer("app_entry", "pkg:run")
pyproject_writer("entry_type", "spec")
pyproject_writer("is_gui", False)
pyproject_writer("python_version", "3.12")

yield tmp_path
Expand Down
1 change: 1 addition & 0 deletions tests/func/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def test_pyproject_parser_rye_project(rye_project):
assert isinstance(parser.name, str)
assert isinstance(parser.version, str)
assert isinstance(parser.rye, dict)
assert isinstance(parser.is_gui, bool)


def test_pyproject_writer_set_entry(tmp_path_chdir):
Expand Down
Loading

0 comments on commit d74c8b0

Please sign in to comment.