Skip to content

Commit 95fc46d

Browse files
authored
Merge pull request #153 from uhd-urz/dev
Merge dev into main
2 parents ad1caad + 2531148 commit 95fc46d

File tree

10 files changed

+107
-76
lines changed

10 files changed

+107
-76
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.3.2] - 2025-06-17
9+
10+
This is a hotfix release.
11+
12+
### Fixed
13+
14+
- Newly installed elAPI, without `elapi.yml` configuration file aborts (
15+
GH https://github.com/uhd-urz/elAPI/issues/151)
16+
817
## [2.3.1] - 2025-05-23
918

1019
This release brings some minor bug fixes and improvements.
@@ -23,7 +32,6 @@ This release brings some minor bug fixes and improvements.
2332

2433
- Migrated from Poetry from uv (GH #148)
2534

26-
2735
## [2.3.0] - 2024-12-21
2836

2937
This release mainly revamps and completes the `bill-teams` plugin that is used for billing eLabFTW usage at the

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "elapi"
3-
version = "2.3.1"
3+
version = "2.3.2"
44
description = "elAPI is a powerful, extensible API client for eLabFTW."
55
authors = [{ name = "Alexander Haller, Mahadi Xion", email = "elabftw@uni-heidelberg.de" }]
66
requires-python = ">=3.9.0,<4"

src/elapi/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.3.1
1+
2.3.2

src/elapi/cli/_plugin_handler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ def get_typer_apps(self) -> Generator[Optional[PluginInfo], None, None]:
339339
try:
340340
module = self.load_plugin(plugin_name, cli_script, project_dir)
341341
except (Exception, BaseException) as e:
342-
if get_development_mode() is True:
342+
if get_development_mode(skip_validation=True) is True:
343343
raise e
344344
message: str = (
345345
f"An exception occurred while trying to load a local "
@@ -387,7 +387,7 @@ def get_typer_apps(self) -> Generator[Optional[PluginInfo], None, None]:
387387
plugin_name, cli_script, project_dir
388388
)
389389
except (Exception, BaseException) as e:
390-
if get_development_mode() is True:
390+
if get_development_mode(skip_validation=True) is True:
391391
raise e
392392
message: str = (
393393
f"An exception occurred while trying to load a local "
@@ -415,7 +415,7 @@ def get_typer_apps(self) -> Generator[Optional[PluginInfo], None, None]:
415415
try:
416416
module = self.load_plugin(plugin_name, cli_script, project_dir)
417417
except (Exception, BaseException) as e:
418-
if get_development_mode() is True:
418+
if get_development_mode(skip_validation=True) is True:
419419
raise e
420420
message: str = (
421421
f"An exception occurred while trying to load a local "

src/elapi/cli/elapi.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,6 @@ def messages_panel():
342342
),
343343
)
344344
grid.add_row(f"{i}.", message)
345-
logger.handlers[-1]._log_render.show_path = True
346345
grid.add_row(
347346
"",
348347
NoteText(

src/elapi/configuration/config.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
import os
33
from pathlib import Path
4-
from typing import Optional
4+
from typing import Optional, Union
55

66
from dynaconf import Dynaconf
77

@@ -83,10 +83,11 @@
8383
env_switcher=f"{env_var_app_name}_ENV",
8484
# environment variable to apply mode of environment (e.g., dev, production)
8585
core_loaders=["YAML"], # will not read any file extensions except YAML
86-
# loaders=['conf'], # will not work without properly defining a custom loader for .conf first
86+
# loaders=['conf'], # will not work without properly defining a custom loader for .conf first
8787
yaml_loader="safe_load", # safe load doesn't execute arbitrary Python code in YAML files
8888
settings_files=[SYSTEM_CONFIG_LOC, LOCAL_CONFIG_LOC, PROJECT_CONFIG_LOC],
89-
# the order of settings_files list is the overwrite priority order. PROJECT_CONFIG_LOC has the highest priority.
89+
# Order of the "settings_files" list is the overwrite priority order.
90+
# PROJECT_CONFIG_LOC has the highest priority.
9091
)
9192

9293
history = ConfigHistory(settings)
@@ -136,13 +137,13 @@ def _mask(self) -> str:
136137
if len(self._token) in r:
137138
expose = expose_table[r]
138139
break
139-
return f"{self.token[:expose]}{self.mask_char * (expose + 1)}{self.token[:-expose-1:-1][::-1]}"
140+
return f"{self.token[:expose]}{self.mask_char * (expose + 1)}{self.token[: -expose - 1 : -1][::-1]}"
140141

141142

142143
# Note elabftw-python uses the term "api_key" for "API_TOKEN"
143144
API_TOKEN: str = settings.get(KEY_API_TOKEN, None)
144145

145-
# Here, bearer term "Authorization" already follows convention, that's why it's not part of the configuration file
146+
# Here, the bearer term "Authorization" already follows convention, that's why it's not part of the configuration file
146147
TOKEN_BEARER: str = "Authorization"
147148
# Reference: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
148149

@@ -171,23 +172,23 @@ def _mask(self) -> str:
171172
# The history is ready to be inspected
172173
inspect = InspectConfigHistory(history)
173174

174-
# UNSAFE_TOKEN_WARNING falls back to True if not defined in configuration
175+
# UNSAFE_TOKEN_WARNING falls back to True if not defined in the configuration
175176
UNSAFE_TOKEN_WARNING_DEFAULT_VAL: bool = True
176177
UNSAFE_TOKEN_WARNING = settings.get(KEY_UNSAFE_TOKEN_WARNING, None)
177178

178-
# ENABLE_HTTP2 falls back to False if not defined in configuration
179+
# ENABLE_HTTP2 falls back to False if not defined in the configuration
179180
ENABLE_HTTP2_DEFAULT_VAL: bool = False
180181
ENABLE_HTTP2 = settings.get(KEY_ENABLE_HTTP2, None)
181182

182-
# VERIFY_SSL falls back to True if not defined in configuration
183+
# VERIFY_SSL falls back to True if not defined in the configuration
183184
VERIFY_SSL_DEFAULT_VAL: bool = True
184185
VERIFY_SSL = settings.get(KEY_VERIFY_SSL, None)
185186

186187
# TIMEOUT falls back to 90.0 seconds if not defined in configuration
187188
TIMEOUT_DEFAULT_VAL: float = 90.0 # from httpx._config import DEFAULT_TIMEOUT_CONFIG
188189
TIMEOUT = settings.get(KEY_TIMEOUT, None)
189190

190-
# DEVELOPMENT_MODE falls back to false if not defined in configuration
191+
# DEVELOPMENT_MODE falls back to false if not defined in the configuration
191192
DEVELOPMENT_MODE_DEFAULT_VAL: bool = False
192193
DEVELOPMENT_MODE = settings.get(KEY_DEVELOPMENT_MODE, None)
193194

@@ -223,7 +224,7 @@ def _mask(self) -> str:
223224

224225
# Temporary data storage location
225226
# This location is not currently used anywhere, for potential future use only.
226-
TMP_DIR: [ProperPath, Path, Missing] = ProperPath(TMP_DIR, err_logger=logger)
227+
TMP_DIR: Union[ProperPath, Path, Missing] = ProperPath(TMP_DIR, err_logger=logger)
227228
try:
228229
TMP_DIR.create()
229230
except TMP_DIR.PathException:

src/elapi/configuration/overridable_vars.py

Lines changed: 51 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,87 +2,94 @@
22
from typing import Union
33

44
from ._overload_history import reinitiate_config
5-
from .config import APIToken
5+
from .config import (
6+
MinimalActiveConfiguration,
7+
APIToken,
8+
KEY_HOST,
9+
ELAB_HOST_URL_API_SUFFIX,
10+
KEY_API_TOKEN,
11+
KEY_EXPORT_DIR,
12+
KEY_UNSAFE_TOKEN_WARNING,
13+
KEY_ENABLE_HTTP2,
14+
KEY_VERIFY_SSL,
15+
KEY_TIMEOUT,
16+
KEY_DEVELOPMENT_MODE,
17+
KEY_PLUGIN_KEY_NAME,
18+
)
619
from ..styles import Missing
720

821

9-
def _run_validation_once() -> None:
10-
if get_development_mode() is False or get_development_mode() == Missing():
11-
reinitiate_config()
12-
13-
14-
def get_active_host() -> str:
15-
from .config import KEY_HOST, MinimalActiveConfiguration
16-
17-
_run_validation_once()
22+
def get_active_host(*, skip_validation: bool = False) -> str:
23+
if not skip_validation:
24+
_development_mode_validation_switch()
1825
return MinimalActiveConfiguration().get_value(KEY_HOST)
1926

2027

21-
def get_active_host_url_without_api_subdir() -> Union[str, Missing]:
28+
def get_active_host_url_without_api_subdir(
29+
*, skip_validation: bool = False
30+
) -> Union[str, Missing]:
2231
import re
23-
from .config import ELAB_HOST_URL_API_SUFFIX
2432

25-
if (host := get_active_host()) != Missing():
33+
if (host := get_active_host(skip_validation=skip_validation)) != Missing():
2634
return re.sub(
2735
ELAB_HOST_URL_API_SUFFIX,
2836
r"",
29-
get_active_host(),
37+
get_active_host(skip_validation=skip_validation),
3038
count=1,
3139
flags=re.IGNORECASE,
3240
)
3341
return host
3442

3543

36-
def get_active_api_token() -> APIToken:
37-
from .config import KEY_API_TOKEN, MinimalActiveConfiguration
38-
39-
_run_validation_once()
44+
def get_active_api_token(*, skip_validation: bool = False) -> APIToken:
45+
if not skip_validation:
46+
_development_mode_validation_switch()
4047
return MinimalActiveConfiguration().get_value(KEY_API_TOKEN)
4148

4249

43-
def get_active_export_dir() -> Path:
44-
from .config import KEY_EXPORT_DIR, MinimalActiveConfiguration
45-
46-
_run_validation_once()
50+
def get_active_export_dir(*, skip_validation: bool = False) -> Path:
51+
if not skip_validation:
52+
_development_mode_validation_switch()
4753
return MinimalActiveConfiguration().get_value(KEY_EXPORT_DIR)
4854

4955

50-
def get_active_unsafe_token_warning() -> bool:
51-
from .config import KEY_UNSAFE_TOKEN_WARNING, MinimalActiveConfiguration
52-
53-
_run_validation_once()
56+
def get_active_unsafe_token_warning(*, skip_validation: bool = False) -> bool:
57+
if not skip_validation:
58+
_development_mode_validation_switch()
5459
return MinimalActiveConfiguration().get_value(KEY_UNSAFE_TOKEN_WARNING)
5560

5661

57-
def get_active_enable_http2() -> bool:
58-
from .config import KEY_ENABLE_HTTP2, MinimalActiveConfiguration
59-
60-
_run_validation_once()
62+
def get_active_enable_http2(*, skip_validation: bool = False) -> bool:
63+
if not skip_validation:
64+
_development_mode_validation_switch()
6165
return MinimalActiveConfiguration().get_value(KEY_ENABLE_HTTP2)
6266

6367

64-
def get_active_verify_ssl() -> bool:
65-
from .config import KEY_VERIFY_SSL, MinimalActiveConfiguration
66-
67-
_run_validation_once()
68+
def get_active_verify_ssl(*, skip_validation: bool = False) -> bool:
69+
if not skip_validation:
70+
_development_mode_validation_switch()
6871
return MinimalActiveConfiguration().get_value(KEY_VERIFY_SSL)
6972

7073

71-
def get_active_timeout() -> float:
72-
from .config import KEY_TIMEOUT, MinimalActiveConfiguration
73-
74-
_run_validation_once()
74+
def get_active_timeout(*, skip_validation: bool = False) -> float:
75+
if not skip_validation:
76+
_development_mode_validation_switch()
7577
return MinimalActiveConfiguration().get_value(KEY_TIMEOUT)
7678

7779

78-
def get_development_mode() -> bool:
79-
from .config import KEY_DEVELOPMENT_MODE, MinimalActiveConfiguration
80+
def _development_mode_validation_switch() -> None:
81+
_value = MinimalActiveConfiguration().get_value(KEY_DEVELOPMENT_MODE)
82+
if _value is False or _value == Missing():
83+
reinitiate_config()
8084

81-
return MinimalActiveConfiguration().get_value(KEY_DEVELOPMENT_MODE)
8285

86+
def get_development_mode(*, skip_validation: bool = False) -> bool:
87+
if not skip_validation:
88+
_development_mode_validation_switch()
89+
return MinimalActiveConfiguration().get_value(KEY_DEVELOPMENT_MODE)
8390

84-
def get_active_plugin_configs() -> dict:
85-
from .config import KEY_PLUGIN_KEY_NAME, MinimalActiveConfiguration
8691

87-
_run_validation_once()
92+
def get_active_plugin_configs(*, skip_validation: bool = False) -> dict:
93+
if not skip_validation:
94+
_development_mode_validation_switch()
8895
return MinimalActiveConfiguration().get_value(KEY_PLUGIN_KEY_NAME)

src/elapi/core_validators/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def __new__(cls, *args, **kwargs):
2727

2828
class RuntimeValidationError(Exit, ValidationError):
2929
def __init__(self, *args) -> None:
30-
super().__init__(*args or (1,)) # default error code is always 1
30+
super().__init__(*args or (1,)) # the default error code is always 1
3131

3232

3333
class CriticalValidationError(Exit, ValidationError): ...

src/elapi/plugins/bill_teams/configuration.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
from pathlib import Path
22

33
from .names import CONFIG_PLUGIN_NAME, PLUGIN_LINK, CONFIG_KEY_ROOT_DIR
4-
from ...configuration import get_active_plugin_configs, CONFIG_FILE_NAME, ConfigurationValidation
4+
from ...configuration import (
5+
get_active_plugin_configs,
6+
CONFIG_FILE_NAME,
7+
ConfigurationValidation,
8+
)
59
from ...core_validators import Validator, Validate, CriticalValidationError
610
from ...loggers import Logger
711
from ...path import ProperPath
812

913
logger = Logger()
1014

11-
bill_teams_plugin_config: dict = get_active_plugin_configs().get(
12-
CONFIG_PLUGIN_NAME, dict()
13-
)
14-
1515

1616
class RootDirConfigurationValidator(ConfigurationValidation, Validator):
1717
ALREADY_VALIDATED: bool = False
1818
__slots__ = ()
1919

2020
def __init__(self):
21+
bill_teams_plugin_config: dict = get_active_plugin_configs().get(
22+
CONFIG_PLUGIN_NAME, dict()
23+
)
2124
super().__init__(bill_teams_plugin_config)
2225

2326
def validate(self) -> Path:

0 commit comments

Comments
 (0)