Skip to content

Commit

Permalink
Add entry_point to handle custom initialization of hab_gui
Browse files Browse the repository at this point in the history
  • Loading branch information
MHendricks committed Sep 22, 2023
1 parent 13317f0 commit 18877ad
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 116 deletions.
188 changes: 78 additions & 110 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,112 +1,80 @@
# Hab Gui

This is a template project to jump-start the creation of additional project repositories, managed by Blur Studio, on GitHub. Below is a walk-through of various characteristics of the project.

## Package Configuration

### `setup.py`

- Enables the `setuptools` extension `setuptools_scm`. This extension replaces the need to statically define a package's version and instead derives the version from SCM metadata (such as the latest git tag).
- Permit package installation in editable mode. (i.e. `pip install -e .`)

_You should not need to modify the contents `setup.py`._

> _Originally `setup.py` was the primary manor by which to define a package's metadata. It has mostly been replaced by `setup.cfg`._
### `setup.cfg`

- Defines static package metadata such as package description, author, and PyPI classifiers.
- Lists package requirements (`install_requires`).
- Lists auxiliary package requirements (`extras_require`). These can be installed by appending the extras group name (or "all" for all groups) in square-brackets upon install (ex: `pip install -e .[lint]`).
- Configures `flake8`.

_Be sure to update the various properties so they pertain to your project._

### `pyproject.toml`

- Defines build system characteristics, more specifically those pertaining to `setuptools_scm` which provides automatic versioning based off git repo state.
- Configures `pytest`.

_The only property that will need updating is the `write_to` destination for the `version.py`-file._

> _While it would be ideal to fully migrate to `pyproject.toml` there are still several aspects of configuration that have not yet fully migrated to support the finalized [PEP 621] standard._
### `requirements.txt` & `requirements-dev.txt`

- Defines a list of packages required in order to install and run the project's package.
- The `-dev` requirements file lists packages useful during development (ex: `pytest`, `flake8`).

_Keep these up to date with any packages required by the project._

## Coding Style & Formatting

### flake8

Analyses for a number of common errors and syntactical violations. Configuration is provided by a section in `setup.cfg`. For consistency when testing we have pinned the tox and pre-commit versions to `flake8==5.0.4 flake8-bugbear==22.12.6 pep8-naming==0.13.3`.

### black

Conforms code to a set of opinionated formatting standards. For consistency when testing we have pinned the tox and pre-commit version of black to `black==22.12.0`.

## Pre-commit Hooks

To simplify the linting and formatting process a basic configuration of pre-commit has been added. Integrating with Git hooks, pre-commit executes a set of actions before a commit is added to the repository interrupting the commit if any of the hooks fail or make additional changes.

Before you can start using pre-commit you will need to install it via pip (`pip install pre-commit`) and install the Git hook scripts into your local copy of the Git repository (`pre-commit install`). The next time any changes are committed those scripts will execute and run the hooks configured in `.pre-commit-config.yaml`. Below are the hooks currently configured:

| Hook | Description |
|--------------------------|--------------------------------------------------------------------|
| [black] | Conforms code to a set of opinionated formatting standards. |
| [flake8] | Analyses for a number of common errors and syntactical violations. |
| [setup-cfg-fmt] | Applies a consistent format to `setup.cfg` files. |
| [check-json] | Attempts to load all json files to verify syntax. |
| [check-toml] | Attempts to load all toml files to verify syntax. |
| [check-xml] | Attempts to load all xml files to verify syntax. |
| [check-yaml] | Attempts to load all yaml files to verify syntax. |
| [debug-statements] | Ensures there are no debug breakpoints present. |
| [end-of-file-fixer] | Ensures each file has one newline at the end. |
| [requirements-txt-fixer] | Sorts entries in requirements.txt and removes incorrect entries. |
| [trailing-whitespace] | Trims any trailing whitespace from lines. |

For consistency when testing we have pinned the tox and pre-commit versions to `black==22.12.0 flake8==5.0.4 flake8-bugbear==22.12.6 pep8-naming==0.13.3`. When using `pre-commit autoupdate`, do not change those package versions without updating the tox.ini file. To make developing multiple packages easier, changing the pinned versions of these packages should be done for all packages, not just this single package. This way the system python can have these versions installed and ide's that automatically enforce black and flake8 rules are consistent across all projects.

### setup-cfg-fmt

For the moment we are stuck using CentOS for linux workstations which still uses python 3.6. For this reason, we have added `args: [--min-py3-version=3.6]` to the configuration of `setup-cfg-fmt`. Any code intended to(or possibly might) run on user workstations or farm nodes on linux should keep this. This setting keeps the setup.cfg file from being updated to exclude python 3.6. Ideally we can remove python 3.6 requirements in the future.

## GitHub Action Workflows

### Static Analysis

Runs against every push, regardless of branch. Checks the codebase for common errors and syntactical violations with `flake8` and that the code is formatted according to `black`.

### Release

Runs when a new release is created.

## Community Documents

### `CODE_OF_CONDUCT.md`

Describes a set of standards contributors and maintainers alike are intended to follow when interacting with and contributing to projects maintained by Blur Studio.

### `CONTRIBUTING.md`

A kick-start document standardizing the manor by which other developers can contribute to projects maintained by Blur Studio.

### `BUG_REPORT.md`, `FEATURE_REQUEST.md`, & `PULL_REQUEST_TEMPLATE.md`

Templates for reporting issues (bugs or feature requests) and submitting pull requests. Each provide a scaffolding for reporters and contributors to follow, ensuring each request has the appropriate information upon first submission.

[PEP 621]: https://www.python.org/dev/peps/pep-0621/
[black]: https://github.com/psf/black
[flake8]: https://github.com/PyCQA/flake8
[setup-cfg-fmt]: https://github.com/asottile/setup-cfg-fmt
[check-json]: https://github.com/pre-commit/pre-commit-hooks#check-json
[check-toml]: https://github.com/pre-commit/pre-commit-hooks#check-toml
[check-xml]: https://github.com/pre-commit/pre-commit-hooks#check-xml
[check-yaml]: https://github.com/pre-commit/pre-commit-hooks#check-yaml
[debug-statements]: https://github.com/pre-commit/pre-commit-hooks#debug-statements
[end-of-file-fixer]: https://github.com/pre-commit/pre-commit-hooks#end-of-file-fixer
[requirements-txt-fixer]: https://github.com/pre-commit/pre-commit-hooks#requirements-txt-fixer
[trailing-whitespace]: https://github.com/pre-commit/pre-commit-hooks#trailing-whitespace
A graphical user interface built on top of [hab](https://github.com/blurstudio/hab)
to take hab out of the shell.

![image](https://github.com/blurstudio/hab-gui/assets/2424292/c3d8247a-4026-4405-ab9b-9360ac927672)

# Features

- Gui for selecting hab URI's and launching aliases.
- Gui for setting the current uri.
- [hab gui sub-command](#hab-gui-sub-command)
- [habw](#habwexe) command allows using hab without popup consoles on windows.
- Customization of hab-gui using [entry_points](#hab-gui-entry-points) defined
in hab site json files.

# Quickstart

1. Enable the use of [`hab gui`](#hab-gui-sub-command) by adding the entry_point
in your site json file. You can use the example site files that come in the
[hab](https://github.com/blurstudio/hab/blob/main/tests/site_main.json) and
[hab-gui](https://github.com/blurstudio/hab-gui/tree/main/tests/site/hab-gui.json)
repos.

```bash
export HAB_PATHS="/path/to/hab-gui/tests/site/hab-gui.json:/path/to/hab/tests/site_main.json"
```
[hab-gui.json](tests/site/hab-gui.json) extends hab's cli by adding the
[gui comand](#hab-gui-sub-command)

2. Use hab gui to launch the alias launch window.

```bash
hab gui launch
```
Or update the URI saved in the [user prefs](https://github.com/blurstudio/hab#user-prefs).
```bash
hab gui set-uri
```

## hab gui sub-command

Using [hab entry points](https://github.com/blurstudio/hab#hab-entry-points) you
can add a gui sub-command to hab. This allows you to launch hab-gui commands
from the existing hab shell commands.

```json5
{
"prepend": {
"entry_points": {
"cli": {
"gui": "hab_gui.cli:gui"
}
}
}
}
```
This is a minimal [site json file](tests/site/hab-gui.json) enabling the use of `hab gui`
that can be added to your existing site files.

## habw.exe

When hab-gui is installed it adds the command `habw` as a using
[gui_scripts](https://packaging.python.org/en/latest/specifications/entry-points/#use-for-scripts).
This is useful for windows users as it prevents showing a command prompt window
while using the other hab gui features. This exe uses the same cli interface as
hab so you can convert any existing command to using habw. Just keep in mind that
you won't see any text output on windows, so you may want to only use it when
using the `hab gui` sub-command.
## Hab-gui Entry Points
By default hab-gui uses fairly simple gui interfaces like a combo box for URI picking
and simple buttons to launch aliases. Using the
[hab entry points](https://github.com/blurstudio/hab#hab-entry-points) system
you can implement your own widgets extending or completely re-implementing them.
| Feature | Description | Multiple values |
|---|---|---|
| hab_gui_init | Used to customize the init of hab gui's launched from the command line. By default this installs a `sys.excepthook` that captures any python exceptions and shows them in a QMessageBox dialog. See [hab-gui-init.json](tests/site/hab-gui-init.json). | Only the first is used, the rest are discarded. |
26 changes: 22 additions & 4 deletions hab_gui/cli.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import logging

import click
from hab.cli import UriArgument, UriHelpClass

import hab_gui.utils

logger = logging.getLogger(__name__)


def get_application(settings=None, **kwargs):
"""Returns the QApplication instance, creating it if required.
def get_application():
"""Returns the QApplication instance, creating it if required."""
If settings is passed, then the `hab_gui_init` entry point is processed, any
other kwargs are passed to `cli_args` of `hab_gui.utils.entry_point_init`.
"""
from Qt.QtWidgets import QApplication

global app

if settings:
hab_gui.utils.entry_point_init(settings.resolver, "launch", cli_args=kwargs)

# Get the existing app if possible
app = QApplication.instance()
if not app:
# Otherwise create a new QApplication instance
app = QApplication([])

return app
Expand Down Expand Up @@ -39,10 +55,12 @@ def launch(settings, verbosity, uri):
# is returned by UriArgument. Convert that to None.
uri = None

app = get_application()
app = get_application(settings, uri=uri, verbosity=verbosity)

settings.resolver._verbosity_target = "hab-gui"
window = AliasLaunchWindow(settings.resolver, uri=uri, verbosity=verbosity)
window.show()

app.exec_()


Expand All @@ -60,7 +78,7 @@ def set_uri(settings, uri):

# Create the QApplication, app.exec_ currently does not need called due
# to this only using QInputDialog and QMessageBox.
app = get_application() # noqa: F841
app = get_application(settings, uri=uri) # noqa: F841

if uri is not None:
# If the uri was passed, no need to ask the user
Expand Down
13 changes: 13 additions & 0 deletions hab_gui/entry_points/base_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class BaseInit:
"""Base class that can be called by `hab_gui.utils.entry_point_init` to
customize some part of hab_gui based on site configuration.
You don't need to subclass this class, just make sure your class conforms to it.
"""

def __init__(self, resolver, cmd, cli_args=None, **kwargs):
super().__init__()
self.resolver = resolver
self.cmd = cmd
self.cli_args = cli_args
self.kwargs = kwargs
19 changes: 19 additions & 0 deletions hab_gui/entry_points/logging_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import logging
import sys

from .base_init import BaseInit

logger = logging.getLogger(__name__)


class LoggingExceptionInit(BaseInit):
"""Overrides `sys.excepthook` and logs any exceptions raised instead of
raising them. This prevents Qt from closing when an exception is raised.
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
sys.excepthook = self.excepthook

def excepthook(self, cls, exception, tb):
logger.exception("Captured Exception:", exc_info=(cls, exception, tb))
23 changes: 23 additions & 0 deletions hab_gui/entry_points/message_box.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import logging
import traceback

from .logging_exception import LoggingExceptionInit

logger = logging.getLogger(__name__)


class MessageBoxInit(LoggingExceptionInit):
"""Overrides `sys.excepthook` and handles any exceptions raised instead of
raising them. This prevents Qt from closing when an exception is raised.
It logs the traceback and then shows a QMessageBox with the exception raised.
"""

def excepthook(self, cls, exception, tb):
super().excepthook(cls, exception, tb)

from Qt.QtWidgets import QMessageBox

# Show the user that an exception happened.
msg = traceback.format_exception(cls, exception, tb)
msg = "".join(msg)
QMessageBox.critical(None, "Exception", msg)
41 changes: 41 additions & 0 deletions hab_gui/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
def entry_point_init(resolver, cmd, cli_args=None, **kwargs):
"""Used to apply startup configuration via site config.
Example of site config replicating the default:
{
"append": {
"entry_points": {
"hab_gui_init": {
"init": "hab_gui.entry_points.message_box:MessageBoxInit"
}
}
}
}
This uses the site entry_point `hab_gui_init` to initialize a class using
the interface defined by `hab_gui.entry_points.BaseInit`. Defaults to
`hab_gui.entry_points.message_box:MessageBoxInit` which shows a QMessageBox if
any exceptions are raised. This prevents Qt from closing the application due
to unhanded exceptions. If you want to disable this entry point default set
the object reference to a empty string.
Example of disabling entry point:
{"append": {"entry_points": {"hab_gui_init": {"init": ""}}}}`
"""
if cli_args is None:
cli_args = {}

default = {"init": "hab_gui.entry_points.message_box:MessageBoxInit"}

# NOTE: kwargs should be added to allow for future changes to this call
eps = resolver.site.entry_points_for_group("hab_gui_init", default=default)
for ep in eps:
if not ep.value:
# Passing an empty value disables processing this entry point
continue

# Evaluate the entry point and initialize it
func = ep.load()
func(resolver, cmd, cli_args=cli_args, **kwargs)


def make_button_coords(button_list, wrap_length, arrangement):
"""Generates a dict that contains an alias_name key and a value that holds
a 2D coordinate list.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ classifiers = [
]
requires-python = ">=3.6"
dependencies = [
"hab>=0.21.0",
"hab>=0.22.0",
"Qt.py",
]

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
click>=7.1.2
hab>=0.21.0
hab>=0.22.0
9 changes: 9 additions & 0 deletions tests/site/hab-gui-init.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"prepend": {
"entry_points": {
"hab_gui_init": {
"gui": "hab_gui.entry_points.logging_exception:LoggingExceptionInit"
}
}
}
}
12 changes: 12 additions & 0 deletions tests/site/hab-gui.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"prepend": {
"entry_points": {
"cli": {
"gui": "hab_gui.cli:gui"
}
}
},
"set": {
"prefs_default": "--prefs"
}
}

0 comments on commit 18877ad

Please sign in to comment.