importjsonimportloggingimportosimportwarningsfromdataclassesimportasdict,dataclass,fieldsfrompathlibimportPathfromtypingimportClassVar,Optional,Setimportyamlfrom.exceptionsimportBeakerConfigurationErrorDEFAULT_CONFIG_LOCATION:Optional[Path]=NoneDEFAULT_INTERNAL_CONFIG_LOCATION:Optional[Path]=Nonetry:DEFAULT_CONFIG_LOCATION=Path.home()/".beaker"/"config.yml"DEFAULT_INTERNAL_CONFIG_LOCATION=Path.home()/".beaker"/".beaker-py.json"exceptRuntimeError:# Can't locate home directory.pass__all__=["Config"]logger=logging.getLogger(__name__)
[docs]@dataclassclassConfig:user_token:str""" Beaker user token that can be obtained from `beaker.org <https://beaker.org/user>`_. """agent_address:str="https://beaker.org"""" The address of the Beaker server. """rpc_address:str="beaker.org:443"""" The address of the Beaker gRPC server. """default_org:Optional[str]="ai2"""" Default Beaker organization to use. """default_workspace:Optional[str]=None""" Default Beaker workspace to use. """default_image:Optional[str]=None""" The default image used for interactive sessions. """ADDRESS_KEY:ClassVar[str]="BEAKER_ADDR"CONFIG_PATH_KEY:ClassVar[str]="BEAKER_CONFIG"TOKEN_KEY:ClassVar[str]="BEAKER_TOKEN"IGNORE_FIELDS:ClassVar[Set[str]]={"updater_timestamp","updater_message"}def__post_init__(self):ifself.default_orgisnotNoneand(self.default_org)==0:self.default_org=Noneifself.default_workspaceisnotNoneandlen(self.default_workspace)==0:self.default_workspace=Noneifself.default_imageisnotNoneandlen(self.default_image)==0:self.default_image=Nonedef__str__(self)->str:fields_str="user_token=***, "+", ".join([f"{f.name}={getattr(self,f.name)}"forfinfields(self)iff.name!="user_token"])returnf"{self.__class__.__name__}({fields_str})"
[docs]@classmethoddeffrom_env(cls,**overrides)->"Config":""" Initialize a config from environment variables or a local config file if one can be found. .. note:: Environment variables take precedence over values in the config file. """config:Configpath=cls.find_config()ifpathisnotNone:config=cls.from_path(path)ifcls.TOKEN_KEYinos.environ:config.user_token=os.environ[cls.TOKEN_KEY]elifcls.TOKEN_KEYinos.environ:config=cls(user_token=os.environ[cls.TOKEN_KEY],)elif"user_token"inoverrides:config=cls(user_token=overrides["user_token"])else:raiseBeakerConfigurationError(f"Failed to find config file or environment variable '{cls.TOKEN_KEY}'")# Override with environment variables.ifcls.ADDRESS_KEYinos.environ:config.agent_address=os.environ[cls.ADDRESS_KEY]# Override with any arguments passed to this method.forname,valueinoverrides.items():ifhasattr(config,name):setattr(config,name,value)else:raiseBeakerConfigurationError(f"Beaker config has no attribute '{name}'")ifnotconfig.user_token:raiseBeakerConfigurationError("Invalid Beaker user token, token is empty")returnconfig
[docs]@classmethoddeffrom_path(cls,path:Path)->"Config":""" Initialize a config from a local config file. """withopen(path)asconfig_file:logger.debug("Loading beaker config from '%s'",path)field_names={f.nameforfinfields(cls)}data=yaml.load(config_file,Loader=yaml.SafeLoader)forkeyinlist(data.keys()):ifkeyincls.IGNORE_FIELDS:data.pop(key)continuevalue=data[key]ifkeynotinfield_names:deldata[key]warnings.warn(f"Unknown field '{key}' found in config '{path}'. "f"If this is a bug, please report it at https://github.com/allenai/beaker-py/issues/new/",RuntimeWarning,)elifisinstance(value,str)andvalue=="":# Replace empty strings with `None`data[key]=Nonereturncls(**data)
[docs]defsave(self,path:Optional[Path]=None):""" Save the config to the given path. """ifpathisNone:ifself.CONFIG_PATH_KEYinos.environ:path=Path(os.environ[self.CONFIG_PATH_KEY])elifDEFAULT_CONFIG_LOCATIONisnotNone:path=DEFAULT_CONFIG_LOCATIONifpathisNone:raiseValueError("param 'path' is required")path.parent.mkdir(parents=True,exist_ok=True)withopen(path,"w")asconfig_file:yaml.dump(asdict(self),config_file)