Skip to content

Commit

Permalink
Use dynaconf for user preferences
Browse files Browse the repository at this point in the history
  • Loading branch information
gselzer committed Sep 6, 2022
1 parent 99a115c commit e12d6f8
Show file tree
Hide file tree
Showing 14 changed files with 74 additions and 54 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,6 @@ venv/
# Scripts
/scripts/*
!/scripts/examples

# Ignore dynaconf secret files
.secrets.*
1 change: 1 addition & 0 deletions dev-environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ channels:
- defaults
dependencies:
# Project dependencies
- dynaconf
- labeling >= 0.1.12
- magicgui >= 0.5.1
- napari
Expand Down
1 change: 1 addition & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ channels:
- defaults
dependencies:
# Project depenencies
- dynaconf
- labeling >= 0.1.12
- magicgui >= 0.5.1
- napari
Expand Down
15 changes: 0 additions & 15 deletions settings.yml

This file was deleted.

1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ package_dir =

# add your package requirements here
install_requires =
dynaconf
labeling >= 0.1.12
napari
magicgui >= 0.5.1
Expand Down
18 changes: 3 additions & 15 deletions src/napari_imagej/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@
"""
import os
import sys
from functools import lru_cache
from multiprocessing.pool import AsyncResult, ThreadPool
from typing import Any, Callable, Dict
from typing import Callable

import imagej
import yaml
from jpype import JClass
from scyjava import config, jimport

from napari_imagej.settings import preferences
from napari_imagej.utilities.logging import log_debug

# -- ImageJ API -- #
Expand All @@ -48,17 +47,6 @@ def ensure_jvm_started() -> None:
ij_future.wait()


def setting(name: str):
"""Gets the value of setting name"""
return settings().get(name, None)


@lru_cache(maxsize=None)
def settings() -> Dict[Any, Any]:
"""Gets all plugin settings as a dictionary"""
return yaml.safe_load(open("settings.yml", "r"))


def get_mode() -> str:
"""
Returns the mode ImageJ will be run in
Expand Down Expand Up @@ -87,7 +75,7 @@ def _imagej_init():

# Configure PyImageJ settings
settings = {
"ij_dir_or_version_or_endpoint": setting("imagej_installation"),
"ij_dir_or_version_or_endpoint": preferences.imagej_directory_or_endpoint,
"mode": get_mode(),
"add_legacy": False,
}
Expand Down
13 changes: 13 additions & 0 deletions src/napari_imagej/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from pathlib import Path

from dynaconf import Dynaconf

# Preferences settings

# The settings are in the root directory, three directories up
SETTINGS_PATH = (Path(__file__).parent / "settings.toml").resolve()

preferences = Dynaconf(
envvar_prefix="DYNACONF",
settings_files=SETTINGS_PATH,
)
18 changes: 18 additions & 0 deletions src/napari_imagej/settings.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# napari-imagej Settings

# USERS BEWARE:
# This toml file will soon be replaced with napari's contribution configuration.

# Path to a local ImageJ2 installation (e.g. /Applications/Fiji.app),
# OR version of net.imagej:imagej artifact to launch (e.g. 2.3.0),
# OR endpoint of another artifact built on ImageJ2 (e.g. sc.fiji:fiji),
# OR list of Maven artifacts to include (e.g.
# ['net.imagej:imagej:2.3.0', 'net.imagej:imagej-legacy', 'net.preibisch:BigStitcher']).
# The default is the latest version of ImageJ2.
imagej_directory_or_endpoint = "net.imagej:imagej"

# This can be used to identify whether transferred data between ImageJ2 and napari
# should be selected via activation or by user selection via a dialog.
# By default, the active layer/window is chosen for transfer between applications.
# By setting this value to false, a popup will be shown instead.
choose_active_layer = true
21 changes: 8 additions & 13 deletions src/napari_imagej/widgets/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,10 @@
from qtpy.QtGui import QIcon, QPixmap
from qtpy.QtWidgets import QHBoxLayout, QMessageBox, QPushButton, QWidget

from napari_imagej.java import (
ensure_jvm_started,
ij,
jc,
log_debug,
running_headless,
setting,
)
from napari_imagej.java import ensure_jvm_started, ij, jc, log_debug, running_headless
from napari_imagej.settings import preferences
from napari_imagej.utilities._module_utils import _get_layers_hack
from napari_imagej.widgets.resources import resource_path


class NapariImageJMenu(QWidget):
Expand Down Expand Up @@ -134,7 +129,7 @@ def __init__(self, viewer: Viewer):
icon = QColoredSVGIcon.from_resources("long_right_arrow")
self.setIcon(icon.colored(theme=viewer.theme))
self.setToolTip("Export active napari layer to ImageJ2")
if setting("choose_active_layer"):
if preferences.choose_active_layer:
self.clicked.connect(self.send_active_layer)
else:
self.clicked.connect(self.send_chosen_layer)
Expand Down Expand Up @@ -179,7 +174,7 @@ def __init__(self, viewer: Viewer):
icon = QColoredSVGIcon.from_resources("long_left_arrow")
self.setIcon(icon.colored(theme=viewer.theme))
self.setToolTip("Import active ImageJ2 Dataset to napari")
if setting("choose_active_layer"):
if preferences.choose_active_layer:
self.clicked.connect(self.get_active_layer)
else:
self.clicked.connect(self.get_chosen_layer)
Expand Down Expand Up @@ -259,19 +254,19 @@ def post_setup():
Thread(target=post_setup).start()

def _setup_headful(self):
self._set_icon("resources/16x16-flat-disabled.png")
self._set_icon(resource_path("imagej2-16x16-flat-disabled"))
self.setToolTip("Display ImageJ2 GUI (loading)")

def post_setup():
ensure_jvm_started()
self._set_icon("resources/16x16-flat.png")
self._set_icon(resource_path("imagej2-16x16-flat"))
self.setEnabled(True)
self.setToolTip("Display ImageJ2 GUI")

Thread(target=post_setup).start()

def _setup_headless(self):
self._set_icon("resources/16x16-flat-disabled.png")
self._set_icon(resource_path("imagej2-16x16-flat-disabled"))
self.setToolTip("ImageJ2 GUI unavailable!")

def disable_popup(self):
Expand Down
16 changes: 16 additions & 0 deletions src/napari_imagej/widgets/resources/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
A module used to help find napari-imagej widget resources
"""
from pathlib import Path

PATH = Path(__file__).parent.resolve()
RESOURCES = {x.stem: str(x) for x in PATH.iterdir() if x.suffix != ".py"}


def resource_path(name: str) -> str:
"""Return path to a resource in this folder."""
if name not in RESOURCES:
raise ValueError(
f"{name} is not a known resource! Known resources: {RESOURCES}"
)
return RESOURCES[name]
File renamed without changes
File renamed without changes
16 changes: 7 additions & 9 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest
from napari import Viewer

from napari_imagej.widgets import menu
from napari_imagej.settings import preferences
from napari_imagej.widgets.menu import NapariImageJMenu
from napari_imagej.widgets.napari_imagej import NapariImageJWidget

Expand Down Expand Up @@ -46,10 +46,8 @@ def gui_widget(viewer) -> Generator[NapariImageJMenu, None, None]:

# Define GUIWidget settings for this particular feature.
# In particular, we want to enforce active layer selection
def mock_setting(value: str):
return {"imagej_installation": None, "choose_active_layer": True}[value]

menu.setting = mock_setting
previous = preferences.choose_active_layer
preferences.choose_active_layer = True

# Create widget
widget: NapariImageJMenu = NapariImageJMenu(viewer)
Expand All @@ -58,6 +56,7 @@ def mock_setting(value: str):

# Cleanup -> Close the widget, trigger ImageJ shutdown
widget.close()
preferences.choose_active_layer = previous


@pytest.fixture
Expand All @@ -68,10 +67,8 @@ def gui_widget_chooser(viewer) -> Generator[NapariImageJMenu, None, None]:

# Define GUIWidget settings for this particular feature.
# In particular, we want to enforce user layer selection via Dialog
def mock_setting(value: str):
return {"imagej_installation": None, "choose_active_layer": False}[value]

menu.setting = mock_setting
previous = preferences.choose_active_layer
preferences.choose_active_layer = False

# Create widget
widget: NapariImageJMenu = NapariImageJMenu(viewer)
Expand All @@ -80,6 +77,7 @@ def mock_setting(value: str):

# Cleanup -> Close the widget, trigger ImageJ shutdown
widget.close()
preferences.choose_active_layer = previous


@pytest.fixture()
Expand Down
5 changes: 3 additions & 2 deletions tests/widgets/test_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
NapariImageJMenu,
ToIJButton,
)
from napari_imagej.widgets.resources import resource_path


class JavaClassesTest(JavaClasses):
Expand Down Expand Up @@ -119,7 +120,7 @@ def test_GUIButton_layout_headful(qtbot, asserter, ij, gui_widget: NapariImageJM
"""Tests headful-specific settings of GUIButton"""
button: GUIButton = gui_widget.gui_button

expected: QPixmap = QPixmap("resources/16x16-flat.png")
expected: QPixmap = QPixmap(resource_path("imagej2-16x16-flat"))
actual: QPixmap = button.icon().pixmap(expected.size())
assert expected.toImage() == actual.toImage()

Expand All @@ -142,7 +143,7 @@ def test_GUIButton_layout_headless(asserter, gui_widget: NapariImageJMenu):
"""Tests headless-specific settings of GUIButton"""
button: GUIButton = gui_widget.gui_button

expected: QPixmap = QPixmap("resources/16x16-flat-disabled.png")
expected: QPixmap = QPixmap(resource_path("imagej2-16x16-flat-disabled"))
actual: QPixmap = button.icon().pixmap(expected.size())
assert expected.toImage() == actual.toImage()

Expand Down

0 comments on commit e12d6f8

Please sign in to comment.