1212 $ elapi get users --id <id>
1313"""
1414
15+ import platform
1516import sys
1617from functools import partial
18+ from json import JSONDecodeError
1719from typing import Optional
1820
1921import typer
2729)
2830from .doc import __PARAMETERS__doc__ as docs
2931from .. import APP_NAME
30- from ..configuration import FALLBACK_EXPORT_DIR
32+ from ..configuration import FALLBACK_EXPORT_DIR , get_active_export_dir
3133from ..loggers import Logger , FileLogger
3234from ..plugins .commons .cli_helpers import Typer
3335from ..styles import get_custom_help_text
3739 rich_format_help_with_callback ,
3840 __PACKAGE_IDENTIFIER__ as styles_package_identifier ,
3941)
42+ from ..utils import get_external_python_version , PythonVersionCheckFailed
43+ from ..utils .typer_patches import patch_typer_flag_value
4044
4145logger = Logger ()
4246file_logger = FileLogger ()
4347pretty .install ()
44-
48+ patch_typer_flag_value ()
4549
4650app = Typer ()
4751SENSITIVE_PLUGIN_NAMES : tuple [str , str , str ] = (
@@ -262,7 +266,13 @@ def cli_cleanup_for_third_party_plugins(*args, override_config=None):
262266
263267
264268def disable_plugin (
265- main_app : Typer , / , * , plugin_name : str , err_msg : str , panel_name : str
269+ main_app : Typer ,
270+ / ,
271+ * ,
272+ plugin_name : str ,
273+ err_msg : str ,
274+ panel_name : str ,
275+ short_reason : Optional [str ] = None ,
266276):
267277 import logging
268278 from ..utils import add_message
@@ -272,11 +282,15 @@ def disable_plugin(
272282 if plugin_name == registered_app .typer_instance .info .name :
273283 main_app .registered_groups .pop (i )
274284 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+ )
275289
276290 @main_app .command (
277291 name = plugin_name ,
278292 rich_help_panel = panel_name ,
279- help = "🚫️ Disabled due to name conflict. See `--help` or log file to know more." ,
293+ help = help_message ,
280294 )
281295 def name_conflict_error ():
282296 from ..core_validators import Exit
@@ -512,17 +526,16 @@ def get(
512526 ),
513527 ] = False ,
514528 export : Annotated [
515- Optional [bool ],
529+ Optional [str ],
516530 typer .Option (
517531 "--export" ,
518532 "-e" ,
519533 help = docs ["export" ] + docs ["export_details" ],
520- is_flag = True ,
521- is_eager = True ,
534+ is_flag = False ,
535+ flag_value = "" ,
522536 show_default = False ,
523537 ),
524- ] = False ,
525- _export_dest : Annotated [Optional [str ], typer .Argument (hidden = True )] = None ,
538+ ] = None ,
526539 export_overwrite : Annotated [
527540 bool ,
528541 typer .Option ("--overwrite" , help = docs ["export_overwrite" ], show_default = False ),
@@ -564,8 +577,8 @@ def get(
564577 validate_identity = Validate (HostIdentityValidator ())
565578 validate_identity ()
566579
567- if export is False :
568- _export_dest = None
580+ if export == "" :
581+ export = get_active_export_dir ()
569582 try :
570583 query : dict = get_structured_data (query , option_name = "--query" )
571584 except ValueError :
@@ -575,17 +588,16 @@ def get(
575588 except ValueError :
576589 raise Exit (1 )
577590 data_format , export_dest , export_file_ext = CLIExport (
578- data_format , _export_dest , export_overwrite
591+ data_format , export , export_overwrite
579592 )
580593 if not query :
581594 format = CLIFormat (data_format , styles_package_identifier , export_file_ext )
582595 else :
583596 logger .info (
584597 "When --query is not empty, formatting with '--format/-F' and highlighting are disabled."
585598 )
586- format = CLIFormat (
587- "txt" , styles_package_identifier , None
588- ) # Use "txt" formatting to show binary
599+ highlight_syntax = False
600+ format = CLIFormat ("txt" , styles_package_identifier , None )
589601
590602 try :
591603 session = GETRequest ()
@@ -627,13 +639,29 @@ def get(
627639 raise Exit (1 ) from e
628640 try :
629641 formatted_data = format (response_data := raw_response .json ())
642+ # Because we prioritize the fact that most responses are sent as JSON
630643 except UnicodeDecodeError :
631644 logger .info (
632645 "Response data is in binary (or not UTF-8 encoded). "
633646 "--export/-e will not be able to infer the data format if export path is a directory."
634647 )
635648 formatted_data = format (response_data := raw_response .content )
636- if export :
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 :
637665 if isinstance (response_data , bytes ):
638666 format .name = "binary"
639667 format .convention = "bin"
@@ -643,25 +671,25 @@ def get(
643671 _query_params = "_" .join (map (lambda x : f"{ x [0 ]} ={ x [1 ]} " , query .items ()))
644672 file_name_stub += f"_query_{ _query_params } " if query else ""
645673 file_name_stub = re .sub (r"_{2,}" , "_" , file_name_stub ).rstrip ("_" )
646- export = Export (
674+ export_response = Export (
647675 export_dest ,
648676 file_name_stub = file_name_stub ,
649677 file_extension = format .convention ,
650678 format_name = format .name ,
651679 )
652680 if not raw_response .is_success :
653- export (data = formatted_data , verbose = False )
681+ export_response (data = formatted_data , verbose = False )
654682 logger .warning (
655683 "Request was not successful. "
656- f"Response for '{ export .file_name_stub } ' is exported to "
657- f"{ export .destination } anyway in { export .format_name } format."
684+ f"Response for '{ export_response .file_name_stub } ' is exported to "
685+ f"{ export_response .destination } anyway in { export_response .format_name } format."
658686 )
659687 raise Exit (1 )
660688 else :
661- export (data = formatted_data , verbose = False )
689+ export_response (data = formatted_data , verbose = False )
662690 logger .info (
663- f"Response for '{ export .file_name_stub } ' is successfully exported to "
664- f"{ export .destination } in { export .format_name } format."
691+ f"Response for '{ export_response .file_name_stub } ' is successfully exported to "
692+ f"{ export_response .destination } in { export_response .format_name } format."
665693 )
666694 else :
667695 if highlight_syntax is True :
@@ -673,6 +701,8 @@ def get(
673701 raise Exit (1 )
674702 stdout_console .print (highlight (formatted_data ))
675703 else :
704+ if isinstance (response_data , bytes ):
705+ formatted_data = response_data
676706 if not raw_response .is_success :
677707 typer .echo (formatted_data , file = sys .stderr )
678708 raise Exit (1 )
@@ -755,7 +785,6 @@ def post(
755785 from ssl import SSLError
756786 from .. import APP_NAME
757787 from ..api import GlobalSharedSession , POSTRequest , ElabFTWURLError
758- from json import JSONDecodeError
759788 from ..core_validators import Validate
760789 from ..api .validators import HostIdentityValidator
761790 from ..plugins .commons import get_location_from_headers
@@ -975,7 +1004,6 @@ def patch(
9751004 from httpx import ConnectError
9761005 from ssl import SSLError
9771006 from ..api import GlobalSharedSession , PATCHRequest , ElabFTWURLError
978- from json import JSONDecodeError
9791007 from ..core_validators import Validate
9801008 from ..api .validators import HostIdentityValidator
9811009 from ..styles import Format , Highlight , NoteText , print_typer_error
@@ -1138,7 +1166,6 @@ def delete(
11381166 from httpx import ConnectError
11391167 from ssl import SSLError
11401168 from ..api import GlobalSharedSession , DELETERequest , ElabFTWURLError
1141- from json import JSONDecodeError
11421169 from ..core_validators import Validate
11431170 from ..api .validators import HostIdentityValidator
11441171 from ..styles import Format , Highlight , NoteText , print_typer_error
@@ -1303,6 +1330,7 @@ def cleanup() -> None:
13031330 plugin_name = app_name ,
13041331 err_msg = error_message ,
13051332 panel_name = THIRD_PARTY_PLUGIN_PANEL_NAME ,
1333+ short_reason = "naming conflict" ,
13061334 )
13071335 elif app_name in INTERNAL_PLUGIN_NAME_REGISTRY :
13081336 error_message = (
@@ -1320,6 +1348,7 @@ def cleanup() -> None:
13201348 plugin_name = app_name ,
13211349 err_msg = error_message ,
13221350 panel_name = INTERNAL_PLUGIN_PANEL_NAME ,
1351+ short_reason = "naming conflict" ,
13231352 )
13241353 elif app_name in RESERVED_PLUGIN_NAMES :
13251354 error_message = (
@@ -1337,8 +1366,47 @@ def cleanup() -> None:
13371366 plugin_name = app_name ,
13381367 err_msg = error_message ,
13391368 panel_name = THIRD_PARTY_PLUGIN_PANEL_NAME ,
1369+ short_reason = "naming conflict" ,
13401370 )
13411371 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
13421410 EXTERNAL_LOCAL_PLUGIN_NAME_REGISTRY [app_name ] = PluginInfo (
13431411 ext_app_obj , _path , _venv , _proj_dir
13441412 )
0 commit comments