Skip to content

Commit

Permalink
Merge pull request #23 from trappitsch/pyapp_app_entry_types
Browse files Browse the repository at this point in the history
Allow for all reasonable PYAPP_EXEC_ entry types
  • Loading branch information
trappitsch authored Mar 8, 2024
2 parents dc2a42f + 89d2d52 commit c54e856
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 18 deletions.
12 changes: 11 additions & 1 deletion src/box/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,30 @@ def cli():
type=str,
)
@click.option("-e", "--entry", help="Set the app entry for the project.")
@click.option(
"-et",
"--entry-type",
help=(
"Set the app entry type (X) to pass to PyApp (`PYAPP_ENTRY_X`) 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, opt_pyapp_vars):
def init(
quiet, builder, optional_deps, 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,
app_entry=entry,
app_entry_type=entry_type,
python_version=python_version,
opt_pyapp_vars=opt_pyapp_vars,
)
Expand Down
5 changes: 5 additions & 0 deletions src/box/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ def app_entry(self):
"""Return the box-saved app entry point."""
return self._pyproject["tool"]["box"]["app_entry"]

@property
def app_entry_type(self):
"""Return the entry type of the project for PyApp."""
return self._pyproject["tool"]["box"]["entry_type"]

@property
def builder(self) -> str:
"""Return the builder of the project."""
Expand Down
31 changes: 27 additions & 4 deletions src/box/initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,15 @@


class InitializeProject:
"""Initialize a new project.
# todo: allow for custom build command, custom dist folder
"""
"""Initialize a new project."""

def __init__(
self,
quiet: bool = False,
builder: str = None,
optional_deps: str = None,
app_entry: str = None,
app_entry_type: str = None,
python_version: str = None,
opt_pyapp_vars: str = None,
):
Expand All @@ -31,6 +29,7 @@ def __init__(
:param builder: Builder tool to use.
:param optional_deps: Optional dependencies for the 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.
:param opt_pyapp_vars: Optional PyApp variables to set.
"""
Expand All @@ -39,6 +38,7 @@ def __init__(
self._optional_deps = optional_deps
self._opt_paypp_vars = opt_pyapp_vars
self._app_entry = app_entry
self._app_entry_type = app_entry_type
self._python_version = python_version

self.app_entry = None
Expand All @@ -54,6 +54,7 @@ def initialize(self):
self._set_builder()
self._set_optional_deps()
self._set_app_entry()
self._set_app_entry_type()
self._set_python_version()
self._set_optional_pyapp_variables()

Expand Down Expand Up @@ -108,6 +109,28 @@ def query_app_entry(query_txt, opts: List) -> None:

pyproject_writer("app_entry", self.app_entry)

def _set_app_entry_type(self):
"""Set the app entry type for the PyApp packaging. Defaults to `spec`."""
if self._app_entry_type:
if self._app_entry_type.lower() not in ut.PYAPP_APP_ENTRY_TYPES:
raise click.ClickException(
f"Invalid entry type. "
f"Please choose from {ut.PYAPP_APP_ENTRY_TYPES}."
)
else:
entry_type = self._app_entry_type.lower()
else:
if self._quiet:
entry_type = "spec"
else:
entry_type = click.prompt(
"Choose an entry type for the project in PyApp.",
type=click.Choice(ut.PYAPP_APP_ENTRY_TYPES),
default="spec",
)

pyproject_writer("entry_type", entry_type)

def _set_builder(self):
"""Set the builder for the project (defaults to rye)."""
possible_builders = PackageApp().builders
Expand Down
7 changes: 5 additions & 2 deletions src/box/packager.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,15 @@ def _set_env(self):
# get the python version or set to default
py_version = self.config.python_version or ut.PYAPP_PYTHON_VERSIONS[-1]

# set variable name for app_entry
app_entry_type = self.config.app_entry_type
var_app_entry = f"PYAPP_EXEC_{app_entry_type.upper()}"

# set variables
os.environ["PYAPP_PROJECT_NAME"] = self.config.name_pkg
os.environ["PYAPP_PROJECT_VERSION"] = self.config.version
os.environ["PYAPP_PROJECT_PATH"] = str(dist_file)
# fixme: this whole thing is a hack. give options for entry, see PyApp docs
os.environ["PYAPP_EXEC_SPEC"] = self.config.app_entry
os.environ[var_app_entry] = self.config.app_entry
os.environ["PYAPP_PYTHON_VERSION"] = py_version
if value := self.config.optional_dependencies:
os.environ["PYAPP_PIP_OPTIONAL_DEPS"] = value
Expand Down
4 changes: 4 additions & 0 deletions src/box/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

from box.config import PyProjectParser

# available app entry types that are used in box
PYAPP_APP_ENTRY_TYPES = ["spec", "module", "script", "notebook"]


# supported Python versions. Default will be set to last entry (latest).
PYAPP_PYTHON_VERSIONS = (
"pypy2.7",
Expand Down
30 changes: 23 additions & 7 deletions tests/cli/test_cli_initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,19 @@


@pytest.mark.parametrize(
"app_entry", ["\n\nhello\n3.8\nPYAPP_FULL_ISOLATION 1", "\ngui\n127\nhello\n\n"]
"app_entry",
["\n\nhello\n\n3.8\nPYAPP_FULL_ISOLATION 1", "\ngui\n127\nhello\nmodule\n\n"],
)
def test_initialize_project_app_entry_typed(rye_project_no_box, app_entry):
"""Initialize a new project."""
# provided app_entry values
app_entry_split = app_entry.split("\n")
deps_exp = app_entry_split[0]
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:
toml_data = fin.read().split("\n")
Expand All @@ -33,26 +42,29 @@ def test_initialize_project_app_entry_typed(rye_project_no_box, app_entry):
assert result.exit_code == 0
assert "Project initialized." in result.output

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

# assert correct app_entry
assert pyproj.app_entry == app_entry_exp

# assert correct app_entry_type
if entry_type_exp == "":
entry_type_exp = "spec" # default value
assert pyproj.app_entry_type == entry_type_exp

# assert that extra dependencies were set
if (deps_exp := app_entry.split("\n")[0]) != "":
if deps_exp != "":
assert pyproj.optional_dependencies == deps_exp

# assert that default builder is set to rye
assert pyproj.builder == "rye"

# assert default python version
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:
Expand All @@ -67,6 +79,7 @@ def test_initialize_with_options(rye_project_no_box):
"""Initialize a new project with options."""
py_version = "3.8"
entry_point = "myapp:entry"
entry_type = "module"
optional_deps = "gui"
builder = "hatch"

Expand All @@ -77,6 +90,8 @@ def test_initialize_with_options(rye_project_no_box):
"init",
"-e",
entry_point,
"-et",
entry_type,
"-py",
py_version,
"-opt",
Expand All @@ -100,6 +115,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.app_entry_type == entry_type
assert pyproj.optional_pyapp_variables == {"PYAPP_FULL_ISOLATION": "1"}


Expand Down Expand Up @@ -135,7 +151,7 @@ def test_initialize_project_wrong_number_of_pyapp_vars(rye_project_no_box):
result = runner.invoke(
cli,
["init"],
input="\n\nhello\n\nPYAPP_FULL_ISOLATION 1 2\nPYAPP_FULL_ISOLATION 1",
input="\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 @@ -53,6 +53,7 @@ def rye_project(tmp_path):
os.chdir(tmp_path)
pyproject_writer("builder", "rye")
pyproject_writer("app_entry", "pkg:run")
pyproject_writer("entry_type", "spec")
pyproject_writer("python_version", "3.12")

yield tmp_path
Expand Down
1 change: 0 additions & 1 deletion tests/func/test_initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
Only functions that are not tested within the CLI are tested here.
"""


from box.config import PyProjectParser
from box.initialization import InitializeProject

Expand Down
8 changes: 5 additions & 3 deletions tests/func/test_packager.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,10 @@ def test_package_pyapp_cargo_and_move(rye_project, mocker, binary_extensions):
assert exp_binary.read_text() == "not really a binary"


@pytest.mark.parametrize("app_entry_type", ut.PYAPP_APP_ENTRY_TYPES)
@pytest.mark.parametrize("opt_deps", ["gui", None])
@pytest.mark.parametrize("opt_pyapp_vars", ["PYAPP_SOMETHING 2", None])
def test_set_env(rye_project, opt_deps, opt_pyapp_vars, mocker):
def test_set_env(rye_project, mocker, app_entry_type, opt_deps, opt_pyapp_vars):
"""Set environment for `PyApp` packaging."""
config = PyProjectParser()
exec_spec = config.app_entry
Expand All @@ -244,6 +245,7 @@ def test_set_env(rye_project, opt_deps, opt_pyapp_vars, mocker):
dist_file.touch()

# write optional deps to the pyproject.toml
pyproject_writer("entry_type", app_entry_type)
if opt_deps:
pyproject_writer("optional_deps", opt_deps)
if opt_pyapp_vars:
Expand All @@ -259,15 +261,15 @@ def test_set_env(rye_project, opt_deps, opt_pyapp_vars, mocker):
assert os.environ["PYAPP_PROJECT_NAME"] == package_name
assert os.environ["PYAPP_PROJECT_VERSION"] == "0.1.0"
assert os.environ["PYAPP_PROJECT_PATH"] == str(dist_file)
assert os.environ["PYAPP_EXEC_SPEC"] == exec_spec
assert os.environ[f"PYAPP_EXEC_{app_entry_type.upper()}"] == exec_spec
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):
def test_set_env_delete_existing(rye_project):
"""Delete existing `PYAPP` environmental variables."""
var_name = "PYAPP_BLA_VARIABLE"
var_value = "test"
Expand Down

0 comments on commit c54e856

Please sign in to comment.