Skip to content

Commit

Permalink
Add support for disabling an existing entry_point by setting it to null
Browse files Browse the repository at this point in the history
  • Loading branch information
MHendricks committed Jan 19, 2024
1 parent 3b6dc37 commit e17f306
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 6 deletions.
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,53 @@ This follows the general rule defined in [duplicate definitions](#duplicate-defi
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.

##### Overriding Entry Points

When using multiple site config files you may end up needing to disable a entry point
in specific conditions. For example you want to enable
[hab-gui's cli integration](https://github.com/blurstudio/hab-gui) by default,
but need to disable it for linux farm nodes that have no gui enabled. If you set
a entry point's value to `null` hab will ignore it and not attempt to load it.

For example if you have your `HAB_PATH` set to `c:\hab\host.json;\\server\share\studio.json`.
- The `host.json` site file is stored on each workstation and adds distros that
are not able to be run from over the network.
- The `studio.json` site file that is shared on the network for ease of deployment.
It is used by all hab users on all platforms to define most of the studio defaults
including adding the hab-gui cli as well as the URI configs.

```json5
// studio.json
{
"append": {
"entry_points": {
"cli": {
"gui": "hab_gui.cli:gui"
}
}
}
}
```

For workstations that don't support a gui you modify the workstations `host.json`
site file, adding a override of the value from `"hab_gui.cli:gui"` to `null`.
```json5
// host.json
{
"append": {
"entry_points": {
"hab.cli": {
"gui": null
}
}
}
}
```
Alternatively, you could create a second host site file named `c:\hab\host_no_gui.json`
put the gui disabling config in that file and on the host's you want to disable
the gui prepend to `HAB_PATHS=c:\hab\host_no_gui.json;c:\hab\host.json;\\server\share\studio.json`.


### Python version

Hab uses shell script files instead of an entry_point executable. This allows it
Expand Down
10 changes: 9 additions & 1 deletion hab/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ def dump(self, verbosity=0, color=None):
ret = "\n".join(ret)
return utils.dump_title("Dump of Site", f"{site_ret}\n{ret}", color=color)

def entry_points_for_group(self, group, default=None, entry_points=None):
def entry_points_for_group(
self, group, default=None, entry_points=None, omit_none=True
):
"""Returns a list of importlib_metadata.EntryPoint objects enabled by
this site config. To import and resolve the defined object call `ep.load()`.
Expand All @@ -98,6 +100,9 @@ def entry_points_for_group(self, group, default=None, entry_points=None):
example: `{"gui": "hab_gui.cli:gui"}`
entry_points (dict, optional): Use this dictionary instead of the one
defined on this Site object.
omit_none (bool, optional): If an entry_point's value is set to null/None
then don't include an EntryPoint object for it in the return. This
allows a second site file to disable a entry_point already set.
"""
# Delay this import to when required. It's faster than pkg_resources but
# no need to pay the import price for it if you are not using it.
Expand All @@ -119,6 +124,9 @@ def entry_points_for_group(self, group, default=None, entry_points=None):
# using it's `entry_points` function. We want the current site configuration
# to define the entry points being loaded not the installed pip packages.
for name, value in ep_defs.items():
if omit_none and value is None:
continue

ep = EntryPoint(name=name, group=group, value=value)
ret.append(ep)
return ret
Expand Down
9 changes: 9 additions & 0 deletions tests/site/site_entry_point_c.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"append": {
"entry_points": {
"hab.cli": {
"test-gui": null
}
}
}
}
46 changes: 41 additions & 5 deletions tests/test_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,32 +482,68 @@ def test_default(self, config_root):
assert ep.value == "case:func"

@pytest.mark.parametrize(
"site_files,import_name,fname",
# Note: The default for omit_none is True
"site_files,import_name,fname,omit_none",
(
(["site/site_entry_point_a.json"], "hab_test_entry_points", "gui"),
(["site/site_entry_point_a.json"], "hab_test_entry_points", "gui", True),
(
["site/site_entry_point_b.json", "site/site_entry_point_a.json"],
"hab_test_entry_points",
"gui_alt",
True,
),
(
["site/site_entry_point_a.json", "site/site_entry_point_b.json"],
"hab_test_entry_points",
"gui",
True,
),
# Tests handling an entry_point value of None
(
# None value is ignored due to order
["site/site_entry_point_a.json", "site/site_entry_point_c.json"],
"hab_test_entry_points",
"gui",
True,
),
(
# None value is used, but ignored due to omit_none setting
["site/site_entry_point_c.json", "site/site_entry_point_a.json"],
None,
None,
True,
),
(
# None value is used, but still returned due to omit_none setting
["site/site_entry_point_c.json", "site/site_entry_point_a.json"],
None,
None,
False,
),
),
)
def test_site_cli(self, config_root, site_files, import_name, fname):
def test_site_cli(self, config_root, site_files, import_name, fname, omit_none):
"""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("hab.cli")
entry_points = site.entry_points_for_group("hab.cli", omit_none=omit_none)

if import_name is None and omit_none is True:
assert len(entry_points) == 0
# Nothing else to test if the value is null
return

assert len(entry_points) == 1

# Test that the `test-gui` `hab.cli` entry point is handled correctly
ep = entry_points[0]
assert ep.name == "test-gui"
assert ep.group == "hab.cli"
assert ep.value == f"{import_name}:{fname}"
if omit_none is False:
assert ep.value is None
# Noting else to test, we can't load a value of None.
return
else:
assert ep.value == f"{import_name}:{fname}"

# Load the module's function
funct = ep.load()
Expand Down

0 comments on commit e17f306

Please sign in to comment.