Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert entry_points to dotted notation #82

Merged
merged 2 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ These are defined in site files with the "entry_points" dictionary.
"prepend": {
"entry_points": {
// Group
"cli": {
"hab.cli": {
// Name: Object Reference
"gui": "hab_test_entry_points:gui"
}
Expand All @@ -427,14 +427,25 @@ These are defined in site files with the "entry_points" dictionary.
See the [Entry points specification data model](https://packaging.python.org/en/latest/specifications/entry-points/#data-model)
for details on each item.

| Feature | Description | Multiple values |
|---|---|---|
| cli | Used by the hab cli to add extra commands | All unique names are used. |
| launch_cls | Used as the default `cls` by `hab.parsers.Config.launch()` to launch aliases from inside of python. This should be a subclass of subprocess.Popen. A [complex alias](#complex-aliases) may override this per alias. Defaults to [`hab.launcher.Launcher`](hab/launcher.py). [Example](tests/site/site_entry_point_a.json) | Only the first is used, the rest are discarded. |
<!-- Tooltips used by the table -->
[tt-group]: ## "The hab feature this entry_point is being used for."
[tt-kwargs]: ## "Any keyword arguments that are passed when called."
[tt-return]: ## "The entry_point can return a value and how it will be used. If not documented, then any returned value is ignored."
[tt-multi]: ## "How having multiple entry_points for this group is handled."
[tt-multi-all]: ## "All uniquely named entry point names for this group are run."
[tt-multi-first]: ## "Only the first entry_point for this group is used, the rest are discarded."

| [Group][tt-group] | Description | [\*\*kwargs][tt-kwargs] | [Return][tt-return] | [Multiple][tt-multi] |
|---|---|---|---|---|
| hab.cli | Used by the hab cli to add extra commands. This is expected to be a `click.command` or `click.group` decorated function. | | | [All][tt-multi-all] |
| hab.launch_cls | Used as the default `cls` by `hab.parsers.Config.launch()` to launch aliases from inside of python. This should be a subclass of subprocess.Popen. A [complex alias](#complex-aliases) may override this per alias. Defaults to [`hab.launcher.Launcher`](hab/launcher.py). [Example](tests/site/site_entry_point_a.json) | | | [First][tt-multi-first] |

The name of each entry point is used to de-duplicate results from multiple site json files.
This follows the general rule defined in [duplicate definitions](#duplicate-definitions).

Entry_point names should start with `hab.` and use `.` between each following word
following the group specification on https://packaging.python.org/en/latest/specifications/entry-points/#data-model.

### Python version

Hab uses shell script files instead of an entry_point executable. This allows it
Expand Down Expand Up @@ -773,7 +784,7 @@ their value is stored under this key.
2. `environment`: A set of env var configuration options. For details on this
format, see [Defining Environments](#defining-environments). This is not
os_specific due to aliases already being defined per-platform.
3. `launch_cls`: If defined this entry_point is used instead of the Site defined
3. `hab.launch_cls`: If defined this entry_point is used instead of the Site defined
or default class specifically for launching this alias.
See [houdini](tests/distros/houdini19.5/19.5.493/.hab.json) for an example.

Expand Down
6 changes: 3 additions & 3 deletions hab/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ class SiteCommandLoader(click.Group):
additional `click.Command` objects to the hab cli.

For example if you add this to your site json file:
`{"append": {"entry_points": {"cli": {"gui": "hab_gui.cli:gui"}}}}`
`{"append": {"entry_points": {"hab.cli": {"gui": "hab_gui.cli:gui"}}}}`

The "gui" key in the innermost dict is used for site resolution so another
site json file can replace a upper level definition. As a general rule, the
Expand All @@ -305,7 +305,7 @@ class SiteCommandLoader(click.Group):
"hab_gui.cli:gui" defines what code to execute. For details on defining this,
see value for `importlib-metadata.EntryPoints`. In practice this results in
`from hab_gui.cli import gui`.
For the `cli` entry points, its expected that the linked function(`gui`)
For the `hab.cli` entry points, its expected that the linked function(`gui`)
is a `click.Command` object.
"""

Expand All @@ -317,7 +317,7 @@ def _cli_entry_points(self, site):
self._ep_cache = []

# And populate the cache
for ep in site.entry_points_for_group("cli"):
for ep in site.entry_points_for_group("hab.cli"):
func = ep.load()
self._ep_cache.append((ep, func))

Expand Down
10 changes: 5 additions & 5 deletions hab/parsers/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def launch(self, alias_name, args=None, blocking=False, cls=None, **kwargs):
`output_stdout` and `output_stderr` properties added to proc.
cls (class, optional): A `subprocess.Popen` compatible class is
initialized and used to run the alias. If not passed, then the
site entry_point `launch_cls` is used if defined. Otherwise the
site entry_point `hab.launch_cls` is used if defined. Otherwise the
`hab.launcher.Launcher` class is used.
**kwargs: Any keyword arguments are passed to subprocess.Popen. If on
windows and using pythonw, prevents showing a command prompt.
Expand All @@ -74,15 +74,15 @@ def launch(self, alias_name, args=None, blocking=False, cls=None, **kwargs):
# Get the subprocess.Popen like class to use to launch the alias
if cls is None:
# Use the entry_point if defined on the alias
alias_cls = alias.get("launch_cls")
alias_cls = alias.get("hab.launch_cls")
if alias_cls:
alias_cls = {"launch_cls": alias_cls}
alias_cls = {"hab.launch_cls": alias_cls}
eps = self.resolver.site.entry_points_for_group(
"launch_cls", entry_points=alias_cls
"hab.launch_cls", entry_points=alias_cls
)
else:
# Otherwise use the global definition from Site
eps = self.resolver.site.entry_points_for_group("launch_cls")
eps = self.resolver.site.entry_points_for_group("hab.launch_cls")

if eps:
cls = eps[0].load()
Expand Down
8 changes: 4 additions & 4 deletions tests/distros/houdini19.5/19.5.493/.hab.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,27 @@
[
"houdini",{
"cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdini.exe",
"launch_cls": {"subprocess": "subprocess:Popen"}
"hab.launch_cls": {"subprocess": "subprocess:Popen"}
}
],
[
"houdini19.5", {
"cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdini.exe",
"min_verbosity": {"global": 1},
"launch_cls": {"subprocess": "subprocess:Popen"}
"hab.launch_cls": {"subprocess": "subprocess:Popen"}
}
],
[
"houdinicore", {
"cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdinicore.exe",
"launch_cls": {"subprocess": "subprocess:Popen"}
"hab.launch_cls": {"subprocess": "subprocess:Popen"}
}
],
[
"houdinicore19.5", {
"cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdinicore.exe",
"min_verbosity": {"global": 1},
"launch_cls": {"subprocess": "subprocess:Popen"}
"hab.launch_cls": {"subprocess": "subprocess:Popen"}
}
],
[
Expand Down
4 changes: 2 additions & 2 deletions tests/site/site_entry_point_a.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"append": {
"entry_points": {
"cli": {
"hab.cli": {
"test-gui": "hab_test_entry_points:gui"
},
"launch_cls": {
"hab.launch_cls": {
"subprocess": "subprocess:Popen"
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/site/site_entry_point_b.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"append": {
"entry_points": {
"cli": {
"hab.cli": {
"test-gui": "hab_test_entry_points:gui_alt"
}
}
Expand Down
12 changes: 6 additions & 6 deletions tests/test_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def test_cls_no_entry_point(resolver):
"""Check that if no entry point is defined, `hab.launcher.Launcher` is
used to launch the alias.
"""
entry_points = resolver.site.entry_points_for_group("launch_cls")
entry_points = resolver.site.entry_points_for_group("hab.launch_cls")
assert len(entry_points) == 0

cfg = resolver.resolve("app/aliased/mod")
Expand All @@ -150,12 +150,12 @@ def test_cls_entry_point(config_root):
site = Site(
[config_root / "site/site_entry_point_a.json", config_root / "site_main.json"]
)
entry_points = site.entry_points_for_group("launch_cls")
entry_points = site.entry_points_for_group("hab.launch_cls")
assert len(entry_points) == 1
# Test that the `test-gui` cli entry point is handled correctly
# Test that the `test-gui` `hab.cli` entry point is handled correctly
ep = entry_points[0]
assert ep.name == "subprocess"
assert ep.group == "launch_cls"
assert ep.group == "hab.launch_cls"
assert ep.value == "subprocess:Popen"

resolver = Resolver(site=site)
Expand Down Expand Up @@ -186,9 +186,9 @@ def test_alias_entry_point(config_root):
proc = cfg.launch("global", blocking=True)
assert type(proc).__name__ == "Popen"

# Check that if the complex alias specifies launch_cls, it is used instead
# Check that if the complex alias specifies hab.launch_cls, it is used instead
# of the site defined or default class.
alias = cfg.frozen_data["aliases"][utils.Platform.name()]["global"]
alias["launch_cls"] = {"subprocess": "tests.test_launch:Topen"}
alias["hab.launch_cls"] = {"subprocess": "tests.test_launch:Topen"}
proc = cfg.launch("global", blocking=True)
assert type(proc).__name__ == "Topen"
6 changes: 3 additions & 3 deletions tests/test_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def walk_dir(current):

@pytest.mark.skip(reason="Find a way to test complex alias evaluation in pytest")
def test_complex_alias_bat(tmpdir, config_root, resolver):
"""This test is a placeholder for a future that can actually call hab's cli
"""This test is a placeholder for a future that can actually call hab's `hab.cli`
and its aliases to check that they function correctly in Batch.

This example text shows that using "hab env" can set an environment variable,
Expand Down Expand Up @@ -210,7 +210,7 @@ def test_complex_alias_bat(tmpdir, config_root, resolver):

@pytest.mark.skip(reason="Find a way to test complex alias evaluation in pytest")
def test_complex_alias_ps1(tmpdir, config_root, resolver):
"""This test is a placeholder for a future that can actually call hab's cli
"""This test is a placeholder for a future that can actually call hab's `hab.cli`
and its aliases to check that they function correctly in PowerShell.

This example text shows that using "hab env" can set an environment variable,
Expand Down Expand Up @@ -247,7 +247,7 @@ def test_complex_alias_ps1(tmpdir, config_root, resolver):

@pytest.mark.skip(reason="Find a way to test complex alias evaluation in pytest")
def test_complex_alias_sh(tmpdir, config_root, resolver):
"""This test is a placeholder for a future that can actually call hab's cli
"""This test is a placeholder for a future that can actually call hab's `hab.cli`
and its aliases to check that they function correctly in Bash.

This example text shows that using "hab env" can set an environment variable,
Expand Down
22 changes: 12 additions & 10 deletions tests/test_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,21 +462,23 @@ def test_reversed(self, config_root):

class TestEntryPoints:
def test_empty_site(self, config_root):
"""Test a site not defining any entry points for cli."""
"""Test a site not defining any entry points for `hab.cli`."""
site = Site([config_root / "site_main.json"])
entry_points = site.entry_points_for_group("cli")
entry_points = site.entry_points_for_group("hab.cli")
assert len(entry_points) == 0

def test_default(self, config_root):
"""Test a site not defining any entry points for cli."""
"""Test a site not defining any entry points for `hab.cli`."""
site = Site([config_root / "site_main.json"])
entry_points = site.entry_points_for_group("cli", default={"test": "case:func"})
entry_points = site.entry_points_for_group(
"hab.cli", default={"test": "case:func"}
)
assert len(entry_points) == 1

# Test that the `test-gui` cli entry point is handled correctly
# Test that the `test-gui` `hab.cli` entry point is handled correctly
ep = entry_points[0]
assert ep.name == "test"
assert ep.group == "cli"
assert ep.group == "hab.cli"
assert ep.value == "case:func"

@pytest.mark.parametrize(
Expand All @@ -496,15 +498,15 @@ def test_default(self, config_root):
),
)
def test_site_cli(self, config_root, site_files, import_name, fname):
"""Test a site defining an entry point for cli, possibly multiple times."""
"""Test a site defining an entry point for `hab.cli`, possibly multiple times."""
site = Site([config_root / f for f in site_files])
entry_points = site.entry_points_for_group("cli")
entry_points = site.entry_points_for_group("hab.cli")
assert len(entry_points) == 1

# Test that the `test-gui` cli entry point is handled correctly
# Test that the `test-gui` `hab.cli` entry point is handled correctly
ep = entry_points[0]
assert ep.name == "test-gui"
assert ep.group == "cli"
assert ep.group == "hab.cli"
assert ep.value == f"{import_name}:{fname}"

# Load the module's function
Expand Down