2828import attr
2929import grpc
3030
31+ # TODO: drop if Python >= 3.11 guaranteed
32+ from exceptiongroup import ExceptionGroup # pylint: disable=redefined-builtin
33+
3134from .common import (
3235 ResourceEntry ,
3336 ResourceMatch ,
5760
5861
5962class Error (Exception ):
60- pass
63+ def __str__ (self ):
64+ return f"Error: { ' ' .join (self .args )} "
6165
6266
6367class UserError (Error ):
@@ -72,6 +76,13 @@ class InteractiveCommandError(Error):
7276 pass
7377
7478
79+ class ErrorGroup (ExceptionGroup ):
80+ def __str__ (self ):
81+ # TODO: drop pylint disable once https://github.com/pylint-dev/pylint/issues/8985 is fixed
82+ errors_combined = "\n " .join (f"- { ' ' .join (e .args )} " for e in self .exceptions ) # pylint: disable=not-an-iterable
83+ return f"{ self .message } :\n { errors_combined } "
84+
85+
7586@attr .s (eq = False )
7687class ClientSession :
7788 """The ClientSession encapsulates all the actions a Client can invoke on
@@ -481,6 +492,17 @@ def get_place(self, place=None):
481492 raise UserError (f"pattern { pattern } matches multiple places ({ ', ' .join (places )} )" )
482493 return self .places [places [0 ]]
483494
495+ def get_place_names_from_env (self ):
496+ """Returns a list of RemotePlace names found in the environment config."""
497+ places = []
498+ for role_config in self .env .config .get_targets ().values ():
499+ resources , _ = target_factory .normalize_config (role_config )
500+ remote_places = resources .get ("RemotePlace" , [])
501+ for place in remote_places :
502+ places .append (place )
503+
504+ return places
505+
484506 def get_idle_place (self , place = None ):
485507 place = self .get_place (place )
486508 if place .acquired :
@@ -684,17 +706,31 @@ def check_matches(self, place):
684706 raise UserError (f"Match { match } has no matching remote resource" )
685707
686708 async def acquire (self ):
709+ errors = []
710+ places = self .get_place_names_from_env () if self .env else [self .args .place ]
711+ for place in places :
712+ try :
713+ await self ._acquire_place (place )
714+ except Error as e :
715+ errors .append (e )
716+
717+ if errors :
718+ if len (errors ) == 1 :
719+ raise errors [0 ]
720+ raise ErrorGroup ("Multiple errors occurred during acquire" , errors )
721+
722+ async def _acquire_place (self , place ):
687723 """Acquire a place, marking it unavailable for other clients"""
688- place = self .get_place ()
724+ place = self .get_place (place )
689725 if place .acquired :
690726 host , user = place .acquired .split ("/" )
691727 allowhelp = f"'labgrid-client -p { place .name } allow { self .gethostname ()} /{ self .getuser ()} ' on { host } ."
692728 if self .getuser () == user :
693729 if self .gethostname () == host :
694- raise UserError ("You have already acquired this place." )
730+ raise UserError (f "You have already acquired place { place . name } ." )
695731 else :
696732 raise UserError (
697- f"You have already acquired this place on { host } . To work simultaneously, execute { allowhelp } "
733+ f"You have already acquired place { place . name } on { host } . To work simultaneously, execute { allowhelp } "
698734 )
699735 else :
700736 raise UserError (
@@ -730,8 +766,22 @@ async def acquire(self):
730766 raise ServerError (e .details ())
731767
732768 async def release (self ):
769+ errors = []
770+ places = self .get_place_names_from_env () if self .env else [self .args .place ]
771+ for place in places :
772+ try :
773+ await self ._release_place (place )
774+ except Error as e :
775+ errors .append (e )
776+
777+ if errors :
778+ if len (errors ) == 1 :
779+ raise errors [0 ]
780+ raise ErrorGroup ("Multiple errors occurred during release" , errors )
781+
782+ async def _release_place (self , place ):
733783 """Release a previously acquired place"""
734- place = self .get_place ()
784+ place = self .get_place (place )
735785 if not place .acquired :
736786 raise UserError (f"place { place .name } is not acquired" )
737787 _ , user = place .acquired .split ("/" )
@@ -2215,11 +2265,11 @@ def main():
22152265 if args .debug :
22162266 traceback .print_exc (file = sys .stderr )
22172267 exitcode = e .exitcode
2218- except Error as e :
2268+ except ( Error , ErrorGroup ) as e :
22192269 if args .debug :
22202270 traceback .print_exc (file = sys .stderr )
22212271 else :
2222- print (f"{ parser .prog } : error: { e } " , file = sys .stderr )
2272+ print (f"{ parser .prog } : { e } " , file = sys .stderr )
22232273 exitcode = 1
22242274 except KeyboardInterrupt :
22252275 exitcode = 1
0 commit comments