elapi 2.2.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {elapi-2.2.0 → elapi-2.3.0}/PKG-INFO +2 -2
- {elapi-2.2.0 → elapi-2.3.0}/README.md +1 -1
- {elapi-2.2.0 → elapi-2.3.0}/pyproject.toml +1 -1
- elapi-2.3.0/src/elapi/VERSION +1 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/cli/elapi.py +94 -26
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/configuration/__init__.py +1 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/configuration/validators.py +42 -124
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/path.py +1 -1
- elapi-2.3.0/src/elapi/plugins/bill_teams/__init__.py +7 -0
- elapi-2.3.0/src/elapi/plugins/bill_teams/_doc.py +80 -0
- elapi-2.3.0/src/elapi/plugins/bill_teams/cli.py +1105 -0
- elapi-2.3.0/src/elapi/plugins/bill_teams/configuration.py +54 -0
- elapi-2.3.0/src/elapi/plugins/bill_teams/generate_table.py +148 -0
- elapi-2.3.0/src/elapi/plugins/bill_teams/names.py +14 -0
- elapi-2.3.0/src/elapi/plugins/bill_teams/registry.py +313 -0
- elapi-2.3.0/src/elapi/plugins/bill_teams/specification.py +235 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/bill_teams/validators.py +85 -11
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/commons/export.py +13 -2
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/commons/get_information.py +2 -1
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/experiments/cli.py +27 -26
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/styles/formats.py +13 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/utils/__init__.py +2 -0
- elapi-2.3.0/src/elapi/utils/typer_patches.py +214 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/utils/utils.py +46 -0
- elapi-2.2.0/src/elapi/VERSION +0 -1
- elapi-2.2.0/src/elapi/plugins/bill_teams/__init__.py +0 -2
- elapi-2.2.0/src/elapi/plugins/bill_teams/_doc.py +0 -43
- elapi-2.2.0/src/elapi/plugins/bill_teams/cli.py +0 -551
- elapi-2.2.0/src/elapi/plugins/bill_teams/invoice.py +0 -75
- elapi-2.2.0/src/elapi/plugins/bill_teams/specification.py +0 -47
- {elapi-2.2.0 → elapi-2.3.0}/LICENSE +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/__init__.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/_names.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/api/__init__.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/api/api.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/api/endpoint.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/api/validators.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/cli/__init__.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/cli/__main__.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/cli/_plugin_handler.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/cli/_venv_state_manager.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/cli/doc.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/configuration/_config_history.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/configuration/_overload_history.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/configuration/config.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/configuration/log_file.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/configuration/overridable_vars.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/core_validators/__init__.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/core_validators/base.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/core_validators/path.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/loggers/__init__.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/loggers/base.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/loggers/handlers/__init__.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/loggers/handlers/file.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/loggers/handlers/stderr.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/__init__.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/bill_teams/bill_teams.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/bill_teams/formats.py +0 -0
- /elapi-2.2.0/src/elapi/plugins/bill_teams/generate_table.py → /elapi-2.3.0/src/elapi/plugins/bill_teams/utils.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/commons/__init__.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/commons/cli_helpers.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/commons/get_data_from_input_or_path.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/commons/get_location_from_headers.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/experiments/__init__.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/experiments/_doc.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/experiments/experiments.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/experiments/formats.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/plugins/show_config.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/styles/__init__.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/styles/_markdown_doc.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/styles/_missing.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/styles/base.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/styles/colors.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/styles/highlight.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/styles/rich_utils.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/utils/messages.py +0 -0
- {elapi-2.2.0 → elapi-2.3.0}/src/elapi/validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: elapi
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: elAPI is a powerful, extensible API client for eLabFTW.
|
|
5
5
|
Home-page: https://www.urz.uni-heidelberg.de/en/service-catalogue/software-and-applications/elabftw
|
|
6
6
|
License: AGPL-3.0-only
|
|
@@ -200,7 +200,7 @@ If both `host` and `api_token` are detected, you are good to go!
|
|
|
200
200
|
### Overriding configuration
|
|
201
201
|
|
|
202
202
|
elAPI now supports `--override/--OC` as a global option that can be used to override the configuration parameters
|
|
203
|
-
as detected by `elapi show-
|
|
203
|
+
as detected by `elapi show-config`. All plugins will also automatically listen to the overridden configuration. This can
|
|
204
204
|
be useful to set certain configurations temporarily. E.g., `elapi --OC '{"timeout": 300"}' get info` will override
|
|
205
205
|
the `timeout` from the configuration files.
|
|
206
206
|
|
|
@@ -156,7 +156,7 @@ If both `host` and `api_token` are detected, you are good to go!
|
|
|
156
156
|
### Overriding configuration
|
|
157
157
|
|
|
158
158
|
elAPI now supports `--override/--OC` as a global option that can be used to override the configuration parameters
|
|
159
|
-
as detected by `elapi show-
|
|
159
|
+
as detected by `elapi show-config`. All plugins will also automatically listen to the overridden configuration. This can
|
|
160
160
|
be useful to set certain configurations temporarily. E.g., `elapi --OC '{"timeout": 300"}' get info` will override
|
|
161
161
|
the `timeout` from the configuration files.
|
|
162
162
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.3.0
|
|
@@ -12,8 +12,10 @@ documented in https://doc.elabftw.net/api/v2/ with ease.
|
|
|
12
12
|
$ elapi get users --id <id>
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
+
import platform
|
|
15
16
|
import sys
|
|
16
17
|
from functools import partial
|
|
18
|
+
from json import JSONDecodeError
|
|
17
19
|
from typing import Optional
|
|
18
20
|
|
|
19
21
|
import typer
|
|
@@ -27,7 +29,7 @@ from ._plugin_handler import (
|
|
|
27
29
|
)
|
|
28
30
|
from .doc import __PARAMETERS__doc__ as docs
|
|
29
31
|
from .. import APP_NAME
|
|
30
|
-
from ..configuration import FALLBACK_EXPORT_DIR
|
|
32
|
+
from ..configuration import FALLBACK_EXPORT_DIR, get_active_export_dir
|
|
31
33
|
from ..loggers import Logger, FileLogger
|
|
32
34
|
from ..plugins.commons.cli_helpers import Typer
|
|
33
35
|
from ..styles import get_custom_help_text
|
|
@@ -37,11 +39,13 @@ from ..styles import (
|
|
|
37
39
|
rich_format_help_with_callback,
|
|
38
40
|
__PACKAGE_IDENTIFIER__ as styles_package_identifier,
|
|
39
41
|
)
|
|
42
|
+
from ..utils import get_external_python_version, PythonVersionCheckFailed
|
|
43
|
+
from ..utils.typer_patches import patch_typer_flag_value
|
|
40
44
|
|
|
41
45
|
logger = Logger()
|
|
42
46
|
file_logger = FileLogger()
|
|
43
47
|
pretty.install()
|
|
44
|
-
|
|
48
|
+
patch_typer_flag_value()
|
|
45
49
|
|
|
46
50
|
app = Typer()
|
|
47
51
|
SENSITIVE_PLUGIN_NAMES: tuple[str, str, str] = (
|
|
@@ -262,7 +266,13 @@ for inter_app_obj in internal_plugin_typer_apps:
|
|
|
262
266
|
|
|
263
267
|
|
|
264
268
|
def disable_plugin(
|
|
265
|
-
main_app: Typer,
|
|
269
|
+
main_app: Typer,
|
|
270
|
+
/,
|
|
271
|
+
*,
|
|
272
|
+
plugin_name: str,
|
|
273
|
+
err_msg: str,
|
|
274
|
+
panel_name: str,
|
|
275
|
+
short_reason: Optional[str] = None,
|
|
266
276
|
):
|
|
267
277
|
import logging
|
|
268
278
|
from ..utils import add_message
|
|
@@ -272,11 +282,15 @@ def disable_plugin(
|
|
|
272
282
|
if plugin_name == registered_app.typer_instance.info.name:
|
|
273
283
|
main_app.registered_groups.pop(i)
|
|
274
284
|
break
|
|
285
|
+
help_message = (
|
|
286
|
+
f"🚫️ Disabled{' due to ' + short_reason if short_reason is not None else ''}. "
|
|
287
|
+
f"See `--help` or log file to know more."
|
|
288
|
+
)
|
|
275
289
|
|
|
276
290
|
@main_app.command(
|
|
277
291
|
name=plugin_name,
|
|
278
292
|
rich_help_panel=panel_name,
|
|
279
|
-
help=
|
|
293
|
+
help=help_message,
|
|
280
294
|
)
|
|
281
295
|
def name_conflict_error():
|
|
282
296
|
from ..core_validators import Exit
|
|
@@ -512,17 +526,16 @@ def get(
|
|
|
512
526
|
),
|
|
513
527
|
] = False,
|
|
514
528
|
export: Annotated[
|
|
515
|
-
Optional[
|
|
529
|
+
Optional[str],
|
|
516
530
|
typer.Option(
|
|
517
531
|
"--export",
|
|
518
532
|
"-e",
|
|
519
533
|
help=docs["export"] + docs["export_details"],
|
|
520
|
-
is_flag=
|
|
521
|
-
|
|
534
|
+
is_flag=False,
|
|
535
|
+
flag_value="",
|
|
522
536
|
show_default=False,
|
|
523
537
|
),
|
|
524
|
-
] =
|
|
525
|
-
_export_dest: Annotated[Optional[str], typer.Argument(hidden=True)] = None,
|
|
538
|
+
] = None,
|
|
526
539
|
export_overwrite: Annotated[
|
|
527
540
|
bool,
|
|
528
541
|
typer.Option("--overwrite", help=docs["export_overwrite"], show_default=False),
|
|
@@ -564,8 +577,8 @@ def get(
|
|
|
564
577
|
validate_identity = Validate(HostIdentityValidator())
|
|
565
578
|
validate_identity()
|
|
566
579
|
|
|
567
|
-
if export
|
|
568
|
-
|
|
580
|
+
if export == "":
|
|
581
|
+
export = get_active_export_dir()
|
|
569
582
|
try:
|
|
570
583
|
query: dict = get_structured_data(query, option_name="--query")
|
|
571
584
|
except ValueError:
|
|
@@ -575,7 +588,7 @@ def get(
|
|
|
575
588
|
except ValueError:
|
|
576
589
|
raise Exit(1)
|
|
577
590
|
data_format, export_dest, export_file_ext = CLIExport(
|
|
578
|
-
data_format,
|
|
591
|
+
data_format, export, export_overwrite
|
|
579
592
|
)
|
|
580
593
|
if not query:
|
|
581
594
|
format = CLIFormat(data_format, styles_package_identifier, export_file_ext)
|
|
@@ -583,9 +596,8 @@ def get(
|
|
|
583
596
|
logger.info(
|
|
584
597
|
"When --query is not empty, formatting with '--format/-F' and highlighting are disabled."
|
|
585
598
|
)
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
) # Use "txt" formatting to show binary
|
|
599
|
+
highlight_syntax = False
|
|
600
|
+
format = CLIFormat("txt", styles_package_identifier, None)
|
|
589
601
|
|
|
590
602
|
try:
|
|
591
603
|
session = GETRequest()
|
|
@@ -627,13 +639,29 @@ def get(
|
|
|
627
639
|
raise Exit(1) from e
|
|
628
640
|
try:
|
|
629
641
|
formatted_data = format(response_data := raw_response.json())
|
|
642
|
+
# Because we prioritize the fact that most responses are sent as JSON
|
|
630
643
|
except UnicodeDecodeError:
|
|
631
644
|
logger.info(
|
|
632
645
|
"Response data is in binary (or not UTF-8 encoded). "
|
|
633
646
|
"--export/-e will not be able to infer the data format if export path is a directory."
|
|
634
647
|
)
|
|
635
648
|
formatted_data = format(response_data := raw_response.content)
|
|
636
|
-
|
|
649
|
+
except JSONDecodeError as e:
|
|
650
|
+
if raw_response.status_code == 200:
|
|
651
|
+
logger.info(
|
|
652
|
+
f"Request was successful, but response data could not be parsed as JSON. "
|
|
653
|
+
f"Response will be read as binary."
|
|
654
|
+
)
|
|
655
|
+
formatted_data = format(response_data := raw_response.content)
|
|
656
|
+
else:
|
|
657
|
+
logger.error(
|
|
658
|
+
f"Request for '{endpoint_name}' data was received by the server but "
|
|
659
|
+
f"request was not successful. Response status: {raw_response.status_code}. "
|
|
660
|
+
f"Exception details: '{e!r}'. "
|
|
661
|
+
f"Response: '{raw_response.text}'"
|
|
662
|
+
)
|
|
663
|
+
raise Exit(1) from e
|
|
664
|
+
if export is not None:
|
|
637
665
|
if isinstance(response_data, bytes):
|
|
638
666
|
format.name = "binary"
|
|
639
667
|
format.convention = "bin"
|
|
@@ -643,25 +671,25 @@ def get(
|
|
|
643
671
|
_query_params = "_".join(map(lambda x: f"{x[0]}={x[1]}", query.items()))
|
|
644
672
|
file_name_stub += f"_query_{_query_params}" if query else ""
|
|
645
673
|
file_name_stub = re.sub(r"_{2,}", "_", file_name_stub).rstrip("_")
|
|
646
|
-
|
|
674
|
+
export_response = Export(
|
|
647
675
|
export_dest,
|
|
648
676
|
file_name_stub=file_name_stub,
|
|
649
677
|
file_extension=format.convention,
|
|
650
678
|
format_name=format.name,
|
|
651
679
|
)
|
|
652
680
|
if not raw_response.is_success:
|
|
653
|
-
|
|
681
|
+
export_response(data=formatted_data, verbose=False)
|
|
654
682
|
logger.warning(
|
|
655
683
|
"Request was not successful. "
|
|
656
|
-
f"Response for '{
|
|
657
|
-
f"{
|
|
684
|
+
f"Response for '{export_response.file_name_stub}' is exported to "
|
|
685
|
+
f"{export_response.destination} anyway in {export_response.format_name} format."
|
|
658
686
|
)
|
|
659
687
|
raise Exit(1)
|
|
660
688
|
else:
|
|
661
|
-
|
|
689
|
+
export_response(data=formatted_data, verbose=False)
|
|
662
690
|
logger.info(
|
|
663
|
-
f"Response for '{
|
|
664
|
-
f"{
|
|
691
|
+
f"Response for '{export_response.file_name_stub}' is successfully exported to "
|
|
692
|
+
f"{export_response.destination} in {export_response.format_name} format."
|
|
665
693
|
)
|
|
666
694
|
else:
|
|
667
695
|
if highlight_syntax is True:
|
|
@@ -673,6 +701,8 @@ def get(
|
|
|
673
701
|
raise Exit(1)
|
|
674
702
|
stdout_console.print(highlight(formatted_data))
|
|
675
703
|
else:
|
|
704
|
+
if isinstance(response_data, bytes):
|
|
705
|
+
formatted_data = response_data
|
|
676
706
|
if not raw_response.is_success:
|
|
677
707
|
typer.echo(formatted_data, file=sys.stderr)
|
|
678
708
|
raise Exit(1)
|
|
@@ -755,7 +785,6 @@ def post(
|
|
|
755
785
|
from ssl import SSLError
|
|
756
786
|
from .. import APP_NAME
|
|
757
787
|
from ..api import GlobalSharedSession, POSTRequest, ElabFTWURLError
|
|
758
|
-
from json import JSONDecodeError
|
|
759
788
|
from ..core_validators import Validate
|
|
760
789
|
from ..api.validators import HostIdentityValidator
|
|
761
790
|
from ..plugins.commons import get_location_from_headers
|
|
@@ -975,7 +1004,6 @@ def patch(
|
|
|
975
1004
|
from httpx import ConnectError
|
|
976
1005
|
from ssl import SSLError
|
|
977
1006
|
from ..api import GlobalSharedSession, PATCHRequest, ElabFTWURLError
|
|
978
|
-
from json import JSONDecodeError
|
|
979
1007
|
from ..core_validators import Validate
|
|
980
1008
|
from ..api.validators import HostIdentityValidator
|
|
981
1009
|
from ..styles import Format, Highlight, NoteText, print_typer_error
|
|
@@ -1138,7 +1166,6 @@ def delete(
|
|
|
1138
1166
|
from httpx import ConnectError
|
|
1139
1167
|
from ssl import SSLError
|
|
1140
1168
|
from ..api import GlobalSharedSession, DELETERequest, ElabFTWURLError
|
|
1141
|
-
from json import JSONDecodeError
|
|
1142
1169
|
from ..core_validators import Validate
|
|
1143
1170
|
from ..api.validators import HostIdentityValidator
|
|
1144
1171
|
from ..styles import Format, Highlight, NoteText, print_typer_error
|
|
@@ -1303,6 +1330,7 @@ for plugin_info in external_local_plugin_typer_apps:
|
|
|
1303
1330
|
plugin_name=app_name,
|
|
1304
1331
|
err_msg=error_message,
|
|
1305
1332
|
panel_name=THIRD_PARTY_PLUGIN_PANEL_NAME,
|
|
1333
|
+
short_reason="naming conflict",
|
|
1306
1334
|
)
|
|
1307
1335
|
elif app_name in INTERNAL_PLUGIN_NAME_REGISTRY:
|
|
1308
1336
|
error_message = (
|
|
@@ -1320,6 +1348,7 @@ for plugin_info in external_local_plugin_typer_apps:
|
|
|
1320
1348
|
plugin_name=app_name,
|
|
1321
1349
|
err_msg=error_message,
|
|
1322
1350
|
panel_name=INTERNAL_PLUGIN_PANEL_NAME,
|
|
1351
|
+
short_reason="naming conflict",
|
|
1323
1352
|
)
|
|
1324
1353
|
elif app_name in RESERVED_PLUGIN_NAMES:
|
|
1325
1354
|
error_message = (
|
|
@@ -1337,8 +1366,47 @@ for plugin_info in external_local_plugin_typer_apps:
|
|
|
1337
1366
|
plugin_name=app_name,
|
|
1338
1367
|
err_msg=error_message,
|
|
1339
1368
|
panel_name=THIRD_PARTY_PLUGIN_PANEL_NAME,
|
|
1369
|
+
short_reason="naming conflict",
|
|
1340
1370
|
)
|
|
1341
1371
|
else:
|
|
1372
|
+
if _venv is not None:
|
|
1373
|
+
try:
|
|
1374
|
+
external_plugin_python_version = get_external_python_version(
|
|
1375
|
+
venv_dir=_venv
|
|
1376
|
+
)[:2]
|
|
1377
|
+
except PythonVersionCheckFailed as e:
|
|
1378
|
+
error_message = (
|
|
1379
|
+
f"Plugin name '{original_name}' from {_path} uses virtual environment "
|
|
1380
|
+
f"{_venv} whose own Python version could not "
|
|
1381
|
+
f"be determined for the following reason: {e}. Plugin will be disabled."
|
|
1382
|
+
)
|
|
1383
|
+
disable_plugin(
|
|
1384
|
+
app,
|
|
1385
|
+
plugin_name=app_name,
|
|
1386
|
+
err_msg=error_message,
|
|
1387
|
+
panel_name=THIRD_PARTY_PLUGIN_PANEL_NAME,
|
|
1388
|
+
short_reason="undetermined .venv Python version",
|
|
1389
|
+
)
|
|
1390
|
+
continue
|
|
1391
|
+
else:
|
|
1392
|
+
if external_plugin_python_version != (
|
|
1393
|
+
own_python_version := platform.python_version_tuple()[:2]
|
|
1394
|
+
):
|
|
1395
|
+
error_message = (
|
|
1396
|
+
f"Plugin name '{original_name}' from {_path} uses virtual environment "
|
|
1397
|
+
f"{_venv} whose Python version (major and minor) "
|
|
1398
|
+
f"'{'.'.join(external_plugin_python_version)}' "
|
|
1399
|
+
f"does not match {APP_NAME}'s own Python version "
|
|
1400
|
+
f"'{'.'.join(own_python_version)}'. Plugin will be disabled."
|
|
1401
|
+
)
|
|
1402
|
+
disable_plugin(
|
|
1403
|
+
app,
|
|
1404
|
+
plugin_name=app_name,
|
|
1405
|
+
err_msg=error_message,
|
|
1406
|
+
panel_name=THIRD_PARTY_PLUGIN_PANEL_NAME,
|
|
1407
|
+
short_reason=".venv Python version conflict",
|
|
1408
|
+
)
|
|
1409
|
+
continue
|
|
1342
1410
|
EXTERNAL_LOCAL_PLUGIN_NAME_REGISTRY[app_name] = PluginInfo(
|
|
1343
1411
|
ext_app_obj, _path, _venv, _proj_dir
|
|
1344
1412
|
)
|
|
@@ -70,6 +70,7 @@ from .overridable_vars import (
|
|
|
70
70
|
get_development_mode,
|
|
71
71
|
get_active_plugin_configs,
|
|
72
72
|
)
|
|
73
|
+
from .validators import ConfigurationValidation
|
|
73
74
|
from .validators import PluginConfigurationValidator as _PluginConfigurationValidator
|
|
74
75
|
|
|
75
76
|
validate_configuration(limited_to=[_PluginConfigurationValidator])
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import Optional, Iterable
|
|
2
|
+
from typing import Optional, Iterable, Union
|
|
3
3
|
|
|
4
4
|
from ._config_history import MinimalActiveConfiguration, FieldValueWithKey
|
|
5
5
|
from ..core_validators import Validator, CriticalValidationError
|
|
@@ -7,25 +7,33 @@ from ..styles import stdout_console, Missing
|
|
|
7
7
|
from ..styles.highlight import NoteText
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def __init__(self, minimal_active_config_obj: MinimalActiveConfiguration, /):
|
|
10
|
+
class ConfigurationValidation:
|
|
11
|
+
def __init__(
|
|
12
|
+
self, minimal_active_config_obj: Union[MinimalActiveConfiguration, dict], /
|
|
13
|
+
):
|
|
15
14
|
self.active_configuration = minimal_active_config_obj
|
|
16
15
|
|
|
17
16
|
@property
|
|
18
|
-
def active_configuration(self) -> MinimalActiveConfiguration:
|
|
17
|
+
def active_configuration(self) -> Union[MinimalActiveConfiguration, dict]:
|
|
19
18
|
return self._active_configuration
|
|
20
19
|
|
|
21
20
|
@active_configuration.setter
|
|
22
21
|
def active_configuration(self, value: MinimalActiveConfiguration):
|
|
23
|
-
if not isinstance(value, MinimalActiveConfiguration):
|
|
22
|
+
if not isinstance(value, (MinimalActiveConfiguration, dict)):
|
|
24
23
|
raise TypeError(
|
|
25
|
-
f"Value must be an instance of
|
|
24
|
+
f"Value must be an instance of "
|
|
25
|
+
f"{MinimalActiveConfiguration.__name__} or {dict.__name__}."
|
|
26
26
|
)
|
|
27
27
|
self._active_configuration = value
|
|
28
28
|
|
|
29
|
+
|
|
30
|
+
class HostConfigurationValidator(ConfigurationValidation, Validator):
|
|
31
|
+
ALREADY_VALIDATED: bool = False
|
|
32
|
+
__slots__ = ()
|
|
33
|
+
|
|
34
|
+
def __init__(self, *args):
|
|
35
|
+
super().__init__(*args)
|
|
36
|
+
|
|
29
37
|
def validate(self) -> str:
|
|
30
38
|
from ..loggers import Logger
|
|
31
39
|
from .config import KEY_HOST
|
|
@@ -81,24 +89,12 @@ class HostConfigurationValidator(Validator):
|
|
|
81
89
|
return host
|
|
82
90
|
|
|
83
91
|
|
|
84
|
-
class APITokenConfigurationValidator(Validator):
|
|
92
|
+
class APITokenConfigurationValidator(ConfigurationValidation, Validator):
|
|
85
93
|
ALREADY_VALIDATED: bool = False
|
|
86
94
|
__slots__ = ()
|
|
87
95
|
|
|
88
|
-
def __init__(self,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@property
|
|
92
|
-
def active_configuration(self) -> MinimalActiveConfiguration:
|
|
93
|
-
return self._active_configuration
|
|
94
|
-
|
|
95
|
-
@active_configuration.setter
|
|
96
|
-
def active_configuration(self, value: MinimalActiveConfiguration):
|
|
97
|
-
if not isinstance(value, MinimalActiveConfiguration):
|
|
98
|
-
raise TypeError(
|
|
99
|
-
f"Value must be an instance of {MinimalActiveConfiguration.__name__}."
|
|
100
|
-
)
|
|
101
|
-
self._active_configuration = value
|
|
96
|
+
def __init__(self, *args):
|
|
97
|
+
super().__init__(*args)
|
|
102
98
|
|
|
103
99
|
def validate(self) -> str:
|
|
104
100
|
from ..loggers import Logger
|
|
@@ -164,24 +160,12 @@ class APITokenConfigurationValidator(Validator):
|
|
|
164
160
|
return api_token
|
|
165
161
|
|
|
166
162
|
|
|
167
|
-
class ExportDirConfigurationValidator(Validator):
|
|
163
|
+
class ExportDirConfigurationValidator(ConfigurationValidation, Validator):
|
|
168
164
|
ALREADY_VALIDATED: bool = False
|
|
169
165
|
__slots__ = ()
|
|
170
166
|
|
|
171
|
-
def __init__(self,
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
@property
|
|
175
|
-
def active_configuration(self) -> MinimalActiveConfiguration:
|
|
176
|
-
return self._active_configuration
|
|
177
|
-
|
|
178
|
-
@active_configuration.setter
|
|
179
|
-
def active_configuration(self, value: MinimalActiveConfiguration):
|
|
180
|
-
if not isinstance(value, MinimalActiveConfiguration):
|
|
181
|
-
raise TypeError(
|
|
182
|
-
f"Value must be an instance of {MinimalActiveConfiguration.__name__}."
|
|
183
|
-
)
|
|
184
|
-
self._active_configuration = value
|
|
167
|
+
def __init__(self, *args):
|
|
168
|
+
super().__init__(*args)
|
|
185
169
|
|
|
186
170
|
def validate(self) -> Path:
|
|
187
171
|
import errno
|
|
@@ -263,33 +247,15 @@ class ExportDirConfigurationValidator(Validator):
|
|
|
263
247
|
return export_dir
|
|
264
248
|
|
|
265
249
|
|
|
266
|
-
class BooleanWithFallbackConfigurationValidator(Validator):
|
|
250
|
+
class BooleanWithFallbackConfigurationValidator(ConfigurationValidation, Validator):
|
|
267
251
|
ALREADY_VALIDATED: bool = False
|
|
268
252
|
__slots__ = ()
|
|
269
253
|
|
|
270
|
-
def __init__(
|
|
271
|
-
|
|
272
|
-
minimal_active_config_obj: MinimalActiveConfiguration,
|
|
273
|
-
/,
|
|
274
|
-
key_name: str,
|
|
275
|
-
fallback_value: bool,
|
|
276
|
-
):
|
|
277
|
-
self.active_configuration = minimal_active_config_obj
|
|
254
|
+
def __init__(self, *args, key_name: str, fallback_value: bool):
|
|
255
|
+
super().__init__(*args)
|
|
278
256
|
self.key_name = key_name
|
|
279
257
|
self.fallback_value = fallback_value
|
|
280
258
|
|
|
281
|
-
@property
|
|
282
|
-
def active_configuration(self) -> MinimalActiveConfiguration:
|
|
283
|
-
return self._active_configuration
|
|
284
|
-
|
|
285
|
-
@active_configuration.setter
|
|
286
|
-
def active_configuration(self, value: MinimalActiveConfiguration):
|
|
287
|
-
if not isinstance(value, MinimalActiveConfiguration):
|
|
288
|
-
raise TypeError(
|
|
289
|
-
f"Value must be an instance of {MinimalActiveConfiguration.__name__}."
|
|
290
|
-
)
|
|
291
|
-
self._active_configuration = value
|
|
292
|
-
|
|
293
259
|
def validate(self) -> bool:
|
|
294
260
|
from ..loggers import Logger
|
|
295
261
|
|
|
@@ -313,33 +279,15 @@ class BooleanWithFallbackConfigurationValidator(Validator):
|
|
|
313
279
|
return value
|
|
314
280
|
|
|
315
281
|
|
|
316
|
-
class DecimalWithFallbackConfigurationValidator(Validator):
|
|
282
|
+
class DecimalWithFallbackConfigurationValidator(ConfigurationValidation, Validator):
|
|
317
283
|
ALREADY_VALIDATED: bool = False
|
|
318
284
|
__slots__ = ()
|
|
319
285
|
|
|
320
|
-
def __init__(
|
|
321
|
-
|
|
322
|
-
minimal_active_config_obj: MinimalActiveConfiguration,
|
|
323
|
-
/,
|
|
324
|
-
key_name: str,
|
|
325
|
-
fallback_value: float,
|
|
326
|
-
):
|
|
327
|
-
self.active_configuration = minimal_active_config_obj
|
|
286
|
+
def __init__(self, *args, key_name: str, fallback_value: float):
|
|
287
|
+
super().__init__(*args)
|
|
328
288
|
self.key_name = key_name
|
|
329
289
|
self.fallback_value = fallback_value
|
|
330
290
|
|
|
331
|
-
@property
|
|
332
|
-
def active_configuration(self) -> MinimalActiveConfiguration:
|
|
333
|
-
return self._active_configuration
|
|
334
|
-
|
|
335
|
-
@active_configuration.setter
|
|
336
|
-
def active_configuration(self, value: MinimalActiveConfiguration):
|
|
337
|
-
if not isinstance(value, MinimalActiveConfiguration):
|
|
338
|
-
raise TypeError(
|
|
339
|
-
f"Value must be an instance of {MinimalActiveConfiguration.__name__}."
|
|
340
|
-
)
|
|
341
|
-
self._active_configuration = value
|
|
342
|
-
|
|
343
291
|
def validate(self) -> float:
|
|
344
292
|
from ..loggers import Logger
|
|
345
293
|
|
|
@@ -363,33 +311,15 @@ class DecimalWithFallbackConfigurationValidator(Validator):
|
|
|
363
311
|
return float(value)
|
|
364
312
|
|
|
365
313
|
|
|
366
|
-
class PluginConfigurationValidator(Validator):
|
|
314
|
+
class PluginConfigurationValidator(ConfigurationValidation, Validator):
|
|
367
315
|
ALREADY_VALIDATED: bool = False
|
|
368
316
|
__slots__ = ()
|
|
369
317
|
|
|
370
|
-
def __init__(
|
|
371
|
-
|
|
372
|
-
minimal_active_config_obj: MinimalActiveConfiguration,
|
|
373
|
-
/,
|
|
374
|
-
key_name: str,
|
|
375
|
-
fallback_value: dict,
|
|
376
|
-
):
|
|
377
|
-
self.active_configuration = minimal_active_config_obj
|
|
318
|
+
def __init__(self, *args, key_name: str, fallback_value: dict):
|
|
319
|
+
super().__init__(*args)
|
|
378
320
|
self.key_name = key_name
|
|
379
321
|
self.fallback_value = fallback_value
|
|
380
322
|
|
|
381
|
-
@property
|
|
382
|
-
def active_configuration(self) -> MinimalActiveConfiguration:
|
|
383
|
-
return self._active_configuration
|
|
384
|
-
|
|
385
|
-
@active_configuration.setter
|
|
386
|
-
def active_configuration(self, value: MinimalActiveConfiguration):
|
|
387
|
-
if not isinstance(value, MinimalActiveConfiguration):
|
|
388
|
-
raise TypeError(
|
|
389
|
-
f"Value must be an instance of {MinimalActiveConfiguration.__name__}."
|
|
390
|
-
)
|
|
391
|
-
self._active_configuration = value
|
|
392
|
-
|
|
393
323
|
def validate(self) -> dict:
|
|
394
324
|
from dynaconf.utils.boxing import DynaBox
|
|
395
325
|
from ..loggers import Logger
|
|
@@ -429,7 +359,7 @@ class PluginConfigurationValidator(Validator):
|
|
|
429
359
|
return value
|
|
430
360
|
|
|
431
361
|
|
|
432
|
-
class MainConfigurationValidator(Validator):
|
|
362
|
+
class MainConfigurationValidator(ConfigurationValidation, Validator):
|
|
433
363
|
ALL_VALIDATORS: list = [
|
|
434
364
|
HostConfigurationValidator,
|
|
435
365
|
APITokenConfigurationValidator,
|
|
@@ -457,7 +387,7 @@ class MainConfigurationValidator(Validator):
|
|
|
457
387
|
):
|
|
458
388
|
from ._config_history import MinimalActiveConfiguration
|
|
459
389
|
|
|
460
|
-
|
|
390
|
+
super().__init__(MinimalActiveConfiguration())
|
|
461
391
|
self.limited_to = limited_to
|
|
462
392
|
|
|
463
393
|
@property
|
|
@@ -480,18 +410,6 @@ class MainConfigurationValidator(Validator):
|
|
|
480
410
|
)
|
|
481
411
|
self._limited_to = value
|
|
482
412
|
|
|
483
|
-
@property
|
|
484
|
-
def active_configuration(self) -> MinimalActiveConfiguration:
|
|
485
|
-
return self._active_configuration
|
|
486
|
-
|
|
487
|
-
@active_configuration.setter
|
|
488
|
-
def active_configuration(self, value: MinimalActiveConfiguration):
|
|
489
|
-
if not isinstance(value, MinimalActiveConfiguration):
|
|
490
|
-
raise TypeError(
|
|
491
|
-
f"Value must be an instance of {MinimalActiveConfiguration.__name__}."
|
|
492
|
-
)
|
|
493
|
-
self._active_configuration = value
|
|
494
|
-
|
|
495
413
|
def validate(self) -> list[FieldValueWithKey]:
|
|
496
414
|
from ..core_validators import Validate, ValidationError
|
|
497
415
|
from .config import KEY_EXPORT_DIR
|
|
@@ -533,8 +451,8 @@ class MainConfigurationValidator(Validator):
|
|
|
533
451
|
unsafe_token_warning = Validate(
|
|
534
452
|
BooleanWithFallbackConfigurationValidator(
|
|
535
453
|
self.active_configuration,
|
|
536
|
-
KEY_UNSAFE_TOKEN_WARNING,
|
|
537
|
-
UNSAFE_TOKEN_WARNING_DEFAULT_VAL,
|
|
454
|
+
key_name=KEY_UNSAFE_TOKEN_WARNING,
|
|
455
|
+
fallback_value=UNSAFE_TOKEN_WARNING_DEFAULT_VAL,
|
|
538
456
|
)
|
|
539
457
|
).get()
|
|
540
458
|
# Update validated_fields after validation
|
|
@@ -549,8 +467,8 @@ class MainConfigurationValidator(Validator):
|
|
|
549
467
|
value = Validate(
|
|
550
468
|
BooleanWithFallbackConfigurationValidator(
|
|
551
469
|
self.active_configuration,
|
|
552
|
-
key_name,
|
|
553
|
-
default_value,
|
|
470
|
+
key_name=key_name,
|
|
471
|
+
fallback_value=default_value,
|
|
554
472
|
)
|
|
555
473
|
).get()
|
|
556
474
|
# Update validated_fields after validation
|
|
@@ -559,8 +477,8 @@ class MainConfigurationValidator(Validator):
|
|
|
559
477
|
timeout = Validate(
|
|
560
478
|
DecimalWithFallbackConfigurationValidator(
|
|
561
479
|
self.active_configuration,
|
|
562
|
-
KEY_TIMEOUT,
|
|
563
|
-
TIMEOUT_DEFAULT_VAL,
|
|
480
|
+
key_name=KEY_TIMEOUT,
|
|
481
|
+
fallback_value=TIMEOUT_DEFAULT_VAL,
|
|
564
482
|
)
|
|
565
483
|
).get()
|
|
566
484
|
# Update validated_fields after validation
|
|
@@ -569,8 +487,8 @@ class MainConfigurationValidator(Validator):
|
|
|
569
487
|
plugin = Validate(
|
|
570
488
|
PluginConfigurationValidator(
|
|
571
489
|
self.active_configuration,
|
|
572
|
-
KEY_PLUGIN_KEY_NAME,
|
|
573
|
-
PLUGIN_DEFAULT_VALUE,
|
|
490
|
+
key_name=KEY_PLUGIN_KEY_NAME,
|
|
491
|
+
fallback_value=PLUGIN_DEFAULT_VALUE,
|
|
574
492
|
)
|
|
575
493
|
).get()
|
|
576
494
|
# Update validated_fields after validation
|
|
@@ -177,7 +177,7 @@ class ProperPath:
|
|
|
177
177
|
self.PathException = exception
|
|
178
178
|
raise os_err
|
|
179
179
|
|
|
180
|
-
def _remove_file(self, _file: Path = None, verbose: bool = False) -> None:
|
|
180
|
+
def _remove_file(self, _file: Optional[Path] = None, verbose: bool = False) -> None:
|
|
181
181
|
file = _file if _file else self.expanded
|
|
182
182
|
if not isinstance(file, Path):
|
|
183
183
|
raise ValueError(
|