Skip to content

himena.profile

AppProfile

Model of a profile.

Parameters:

Name Type Description Default
name str

Name of the profile.

'default'
version str

Version of this profile created.

'0.0.10.dev0'
plugins list[str]

List of plugins to load.

['himena_builtins.qt.console', 'himena_builtins.qt.explorer', 'himena_builtins.qt.favorites', 'himena_builtins.qt.history', 'himena_builtins.qt.output', 'himena_builtins.qt.plot', 'himena_builtins.qt.array', 'himena_builtins.qt.basic', 'himena_builtins.qt.dataframe', 'himena_builtins.qt.image', 'himena_builtins.qt.ipynb', 'himena_builtins.qt.rois', 'himena_builtins.qt.stack', 'himena_builtins.qt.table', 'himena_builtins.qt.text', 'himena_builtins.tools.array', 'himena_builtins.tools.conversions', 'himena_builtins.tools.dataframe', 'himena_builtins.tools.dict', 'himena_builtins.tools.image', 'himena_builtins.tools.others', 'himena_builtins.tools.plot', 'himena_builtins.tools.table', 'himena_builtins.tools.text', 'himena_builtins.io', 'himena_builtins.new', 'himena_builtins.user_modifications']
theme str

Theme to use.

'light-green'
startup_commands list[tuple[str, Optional[dict[str, Any]]]]

Startup commands that will be executed when the app starts.

<dynamic>
keybinding_overrides list[KeyBindingOverride]

Built-in mutable sequence.

If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.

<dynamic>
Source code in src\himena\profile.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
class AppProfile(BaseModel):
    """Model of a profile."""

    name: str = Field(
        default="default",
        description="Name of the profile.",
        frozen=True,
    )
    version: str = Field(
        default_factory=_current_version,
        description="Version of this profile created.",
        frozen=True,
    )
    plugins: list[str] = Field(
        default_factory=_default_plugins, description="List of plugins to load."
    )
    theme: str = Field(default="light-green", description="Theme to use.")
    startup_commands: list[tuple[str, dict[str, Any] | None]] = Field(
        default_factory=list,
        description="Startup commands that will be executed when the app starts.",
    )
    keybinding_overrides: list[KeyBindingOverride] = Field(default_factory=list)
    plugin_configs: dict[str, dict[str, Any]] = Field(default_factory=dict)

    @classmethod
    def from_json(cls, path) -> "AppProfile":
        """Construct an AppProfile from a json file."""
        with open(path) as f:
            data = json.load(f)
        version_saved = Version(data.get("version", "0.0.1"))
        self = cls(**data)
        if version_saved < Version(_current_version()):
            for place, version_added in DEFAULT_PLUGINS:
                if version_added > version_saved and place not in self.plugins:
                    # Add the default plugin that is implemented after the profile was
                    # saved.
                    self.plugins.append(place)
        return self

    @classmethod
    def default(cls, save: bool = False) -> "AppProfile":
        """Return the default profile."""
        prof = AppProfile()
        if save and not (profile_dir() / f"{prof.name}.json").exists():
            prof.save()
        return prof

    def save(self, path: str | Path | None = None) -> None:
        """Save profile as a json file."""
        if path is None:
            path = self.profile_path()
        json_string = json.dumps(self.model_dump(), indent=4)
        with open(path, "w") as f:
            f.write(json_string)
        return None

    def profile_path(self) -> Path:
        """Path to this profile."""
        return profile_dir() / f"{self.name}.json"

    def with_name(self, name: str) -> "AppProfile":
        """Return a new profile with a new name."""
        return self.model_copy(update={"name": name})

    def with_plugins(self, plugins: list[str]) -> "AppProfile":
        """Return a new profile with new plugins."""
        return self.model_copy(update={"plugins": plugins})

    def with_plugin_configs(self, configs: dict[str, dict[str, Any]]) -> "AppProfile":
        """Return a new profile with new plugin configs."""
        return self.model_copy(update={"plugin_configs": configs})

    def with_keybinding_override(self, key: str, command_id: str) -> "AppProfile":
        """Return a new profile with new keybind overrides."""
        _overrides = self.keybinding_overrides.copy()
        for entry in _overrides:
            if entry.command_id == command_id:
                if key:
                    entry.key = key
                else:
                    _overrides.remove(entry)
                break
        else:
            if key:
                _overrides.append(KeyBindingOverride(key=key, command_id=command_id))
        return self.model_copy(update={"keybinding_overrides": _overrides})

    def update_plugin_config(self, plugin_id: str, **kwargs) -> None:
        """Update the config of the plugin specified by `plugin_id`"""
        from himena.plugins import AppActionRegistry
        from himena.plugins.widget_plugins import WidgetCallbackBase

        reg = AppActionRegistry.instance()
        configs = self.plugin_configs.copy()
        # NOTE: during development, keys of cur_config and configs[plugin_id] may
        # differ. `cur_config` has all the keys that should exist in the current
        # implementation.
        cur_config = reg._plugin_default_configs[plugin_id].as_dict()
        if plugin_id in configs:
            # Profile already has the plugin config
            for ckey, cval in configs[plugin_id].items():
                if ckey in cur_config:
                    cur_config[ckey] = cval
        for k, v in kwargs.items():
            if k in cur_config:
                cur_config[k]["value"] = v
        configs[plugin_id] = cur_config
        self.with_plugin_configs(configs).save()

        # update existing dock widgets with the new config
        params = {}
        for key, opt in cur_config.items():
            params[key] = opt["value"]
        if cb := WidgetCallbackBase.instance_for_command_id(plugin_id):
            for dock in cb._all_widgets:
                # the internal widget should always has the method "update_configs"
                dock.update_configs(params)

    @field_validator("name")
    def _validate_name(cls, value):
        # check if value is a valid file name
        if not all(c in ALLOWED_LETTERS for c in value):
            raise ValueError(f"Invalid profile name: {value}")
        return value
default(save=False) classmethod

Return the default profile.

Source code in src\himena\profile.py
137
138
139
140
141
142
143
@classmethod
def default(cls, save: bool = False) -> "AppProfile":
    """Return the default profile."""
    prof = AppProfile()
    if save and not (profile_dir() / f"{prof.name}.json").exists():
        prof.save()
    return prof
from_json(path) classmethod

Construct an AppProfile from a json file.

Source code in src\himena\profile.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
@classmethod
def from_json(cls, path) -> "AppProfile":
    """Construct an AppProfile from a json file."""
    with open(path) as f:
        data = json.load(f)
    version_saved = Version(data.get("version", "0.0.1"))
    self = cls(**data)
    if version_saved < Version(_current_version()):
        for place, version_added in DEFAULT_PLUGINS:
            if version_added > version_saved and place not in self.plugins:
                # Add the default plugin that is implemented after the profile was
                # saved.
                self.plugins.append(place)
    return self
profile_path()

Path to this profile.

Source code in src\himena\profile.py
154
155
156
def profile_path(self) -> Path:
    """Path to this profile."""
    return profile_dir() / f"{self.name}.json"
save(path=None)

Save profile as a json file.

Source code in src\himena\profile.py
145
146
147
148
149
150
151
152
def save(self, path: str | Path | None = None) -> None:
    """Save profile as a json file."""
    if path is None:
        path = self.profile_path()
    json_string = json.dumps(self.model_dump(), indent=4)
    with open(path, "w") as f:
        f.write(json_string)
    return None
update_plugin_config(plugin_id, **kwargs)

Update the config of the plugin specified by plugin_id

Source code in src\himena\profile.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
def update_plugin_config(self, plugin_id: str, **kwargs) -> None:
    """Update the config of the plugin specified by `plugin_id`"""
    from himena.plugins import AppActionRegistry
    from himena.plugins.widget_plugins import WidgetCallbackBase

    reg = AppActionRegistry.instance()
    configs = self.plugin_configs.copy()
    # NOTE: during development, keys of cur_config and configs[plugin_id] may
    # differ. `cur_config` has all the keys that should exist in the current
    # implementation.
    cur_config = reg._plugin_default_configs[plugin_id].as_dict()
    if plugin_id in configs:
        # Profile already has the plugin config
        for ckey, cval in configs[plugin_id].items():
            if ckey in cur_config:
                cur_config[ckey] = cval
    for k, v in kwargs.items():
        if k in cur_config:
            cur_config[k]["value"] = v
    configs[plugin_id] = cur_config
    self.with_plugin_configs(configs).save()

    # update existing dock widgets with the new config
    params = {}
    for key, opt in cur_config.items():
        params[key] = opt["value"]
    if cb := WidgetCallbackBase.instance_for_command_id(plugin_id):
        for dock in cb._all_widgets:
            # the internal widget should always has the method "update_configs"
            dock.update_configs(params)
with_keybinding_override(key, command_id)

Return a new profile with new keybind overrides.

Source code in src\himena\profile.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def with_keybinding_override(self, key: str, command_id: str) -> "AppProfile":
    """Return a new profile with new keybind overrides."""
    _overrides = self.keybinding_overrides.copy()
    for entry in _overrides:
        if entry.command_id == command_id:
            if key:
                entry.key = key
            else:
                _overrides.remove(entry)
            break
    else:
        if key:
            _overrides.append(KeyBindingOverride(key=key, command_id=command_id))
    return self.model_copy(update={"keybinding_overrides": _overrides})
with_name(name)

Return a new profile with a new name.

Source code in src\himena\profile.py
158
159
160
def with_name(self, name: str) -> "AppProfile":
    """Return a new profile with a new name."""
    return self.model_copy(update={"name": name})
with_plugin_configs(configs)

Return a new profile with new plugin configs.

Source code in src\himena\profile.py
166
167
168
def with_plugin_configs(self, configs: dict[str, dict[str, Any]]) -> "AppProfile":
    """Return a new profile with new plugin configs."""
    return self.model_copy(update={"plugin_configs": configs})
with_plugins(plugins)

Return a new profile with new plugins.

Source code in src\himena\profile.py
162
163
164
def with_plugins(self, plugins: list[str]) -> "AppProfile":
    """Return a new profile with new plugins."""
    return self.model_copy(update={"plugins": plugins})

KeyBindingOverride

Parameters:

Name Type Description Default
key str
required
command_id str
required
Source code in src\himena\profile.py
93
94
95
class KeyBindingOverride(BaseModel):
    key: str
    command_id: str

data_dir()

Get the user data directory.

Source code in src\himena\profile.py
28
29
30
31
32
def data_dir() -> Path:
    """Get the user data directory."""
    if not USER_DATA_DIR.exists():
        USER_DATA_DIR.mkdir(parents=True)
    return USER_DATA_DIR

new_app_profile(name)

Create a new profile.

Source code in src\himena\profile.py
242
243
244
245
246
247
248
def new_app_profile(name: str) -> None:
    """Create a new profile."""
    path = profile_dir() / f"{name}.json"
    if path.exists():
        raise ValueError(f"Profile {name!r} already exists.")
    profile = AppProfile.default().with_name(name)
    return profile.save(path)

patch_user_data_dir(path)

Change the user data directory to avoid pytest updates the local state.

Source code in src\himena\profile.py
16
17
18
19
20
21
22
23
24
25
@contextmanager
def patch_user_data_dir(path: str | Path):
    """Change the user data directory to avoid pytest updates the local state."""
    global USER_DATA_DIR
    old = USER_DATA_DIR
    USER_DATA_DIR = Path(path)
    try:
        yield
    finally:
        USER_DATA_DIR = old

remove_app_profile(name)

Remove an existing profile.

Source code in src\himena\profile.py
251
252
253
254
def remove_app_profile(name: str) -> None:
    """Remove an existing profile."""
    path = profile_dir() / f"{name}.json"
    return path.unlink()