From e17f3061987827eaa722745739285faee4db4f14 Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Thu, 18 Jan 2024 19:36:18 -0800 Subject: [PATCH] Add support for disabling an existing entry_point by setting it to null --- README.md | 47 ++++++++++++++++++++++++++++++ hab/site.py | 10 ++++++- tests/site/site_entry_point_c.json | 9 ++++++ tests/test_site.py | 46 +++++++++++++++++++++++++---- 4 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 tests/site/site_entry_point_c.json diff --git a/README.md b/README.md index 5de4649..e404ed7 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/hab/site.py b/hab/site.py index ad18b8e..5b13767 100644 --- a/hab/site.py +++ b/hab/site.py @@ -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()`. @@ -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. @@ -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 diff --git a/tests/site/site_entry_point_c.json b/tests/site/site_entry_point_c.json new file mode 100644 index 0000000..3a0bf11 --- /dev/null +++ b/tests/site/site_entry_point_c.json @@ -0,0 +1,9 @@ +{ + "append": { + "entry_points": { + "hab.cli": { + "test-gui": null + } + } + } +} diff --git a/tests/test_site.py b/tests/test_site.py index 44d7e8f..5a27f20 100644 --- a/tests/test_site.py +++ b/tests/test_site.py @@ -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()