class CondaEnv(object): """A wrapper around conda environment settings file, allows adding/removing conda or pip dependencies to env configuration, and supports load/export those settings from/to yaml files. The generated file is the same format as yaml file generated from `conda env export` command. """ def __init__( self, name: str = None, channels: List[str] = None, dependencies: List[str] = None, default_env_yaml_file: str = None, override_channels: bool = False, ): self._yaml = YAML() self._yaml.default_flow_style = False if default_env_yaml_file: env_yml_file = Path(default_env_yaml_file) if not env_yml_file.is_file(): raise BentoMLException( f"Can not find conda environment config yaml file at: " f"`{default_env_yaml_file}`") self._conda_env = self._yaml.load(env_yml_file) else: self._conda_env = self._yaml.load(DEFAULT_CONDA_ENV_BASE_YAML) if name: self.set_name(name) if override_channels and channels is None: raise BentoMLException( "No `conda_channels` provided while override_channels=True") if channels: self.set_channels(channels, override_channels) if dependencies: self.add_conda_dependencies(dependencies) def set_name(self, name): self._conda_env["name"] = name def add_conda_dependencies(self, conda_dependencies: List[str]): # BentoML uses conda's channel_priority=strict option by default # Adding `dependencies` to beginning of the list to take priority over the # existing conda channels self._conda_env["dependencies"] = (conda_dependencies + self._conda_env["dependencies"]) def set_channels(self, channels: List[str], override_channels=False): if override_channels and "nodefaults" not in channels: channels.append("nodefaults") self._conda_env["channels"] = channels def write_to_yaml_file(self, filepath): with open(filepath, 'wb') as output_yaml: self._yaml.dump(self._conda_env, output_yaml)
def test_conda_env_yml_file_option(tmpdir): conda_env_yml_file = os.path.join(tmpdir, 'environment.yml') with open(conda_env_yml_file, 'wb') as f: f.write(""" name: bentoml-test-conda-env channels: - test-ch-1 - test-ch-2 dependencies: - test-dep-1 """.encode()) @bentoml.env(conda_env_yml_file=conda_env_yml_file) class ServiceWithCondaDeps(bentoml.BentoService): @bentoml.api(input=DataframeInput(), batch=True) def predict(self, df): return df service_with_string = ServiceWithCondaDeps() service_with_string.save_to_dir(str(tmpdir)) from pathlib import Path from bentoml.utils.ruamel_yaml import YAML yaml = YAML() env_yml = yaml.load(Path(os.path.join(tmpdir, 'environment.yml'))) assert 'test-ch-1' in env_yml['channels'] assert 'test-ch-2' in env_yml['channels'] assert 'test-dep-1' in env_yml['dependencies']
def parse_yaml_file_callback(ctx, param, value): # pylint: disable=unused-argument yaml = YAML() yml_content = value.read() try: return yaml.load(yml_content) except Exception: raise click.BadParameter( 'Input value is not recognizable yaml file or yaml content')
class CondaEnv(object): """A wrapper around conda environment settings file, allows adding/removing conda or pip dependencies to env configuration, and supports load/export those settings from/to yaml files. The generated file is the same format as yaml file generated from `conda env export` command. """ def __init__( self, name: str, python_version: str, conda_channels: List[str] = None, conda_dependencies: List[str] = None, ): self._yaml = YAML() self._yaml.default_flow_style = False self._conda_env = self._yaml.load( CONDA_ENV_BASE_YAML.format(name=name, python_version=python_version)) if conda_channels: self.add_channels(conda_channels) if conda_dependencies: self.add_conda_dependencies(conda_dependencies) def set_name(self, name): self._conda_env["name"] = name def get_name(self): return self._conda_env["name"] def add_conda_dependencies(self, conda_dependencies: List[str]): # BentoML uses conda's channel_priority=strict option by default # Adding `conda_dependencies` to beginning of the list to take priority over the # existing conda channels self._conda_env["dependencies"] = (conda_dependencies + self._conda_env["dependencies"]) def add_channels(self, channels: List[str]): for channel_name in channels: if channel_name not in self._conda_env["channels"]: self._conda_env["channels"] += channels else: logger.debug(f"Conda channel {channel_name} already added") def write_to_yaml_file(self, filepath): with open(filepath, 'wb') as output_yaml: self._yaml.dump(self._conda_env, output_yaml)
def test_conda_channels_n_dependencies(tmpdir): @bentoml.env(conda_channels=["bentoml-test-channel"], conda_dependencies=["bentoml-test-lib"]) class ServiceWithCondaDeps(bentoml.BentoService): @bentoml.api(input=DataframeInput(), batch=True) def predict(self, df): return df service_with_string = ServiceWithCondaDeps() service_with_string.save_to_dir(str(tmpdir)) from pathlib import Path from bentoml.utils.ruamel_yaml import YAML yaml = YAML() env_yml = yaml.load(Path(os.path.join(tmpdir, 'environment.yml'))) assert 'bentoml-test-channel' in env_yml['channels'] assert 'bentoml-test-lib' in env_yml['dependencies']
def wrapped_load(*args, **kwargs): if self.packed: logger.warning( "`load` on a 'packed' artifact may lead to unexpected behaviors" ) if self.loaded: logger.warning( "`load` an artifact multiple times may lead to unexpected " "behaviors") # load metadata if exists path = args[0] # load(self, path) meta_path = self._metadata_path(path) if os.path.isfile(meta_path): with open(meta_path) as file: yaml = YAML() yaml_content = file.read() self._metadata = yaml.load(yaml_content) ret = original(*args, **kwargs) # do not set self._loaded if `load` has failed with an exception raised self._loaded = True return ret
class SavedBundleConfig(object): def __init__(self, bento_service=None, kind="BentoService"): self.kind = kind self._yaml = YAML() self._yaml.default_flow_style = False self.config = self._yaml.load( BENTOML_CONFIG_YAML_TEMPLATE.format( kind=self.kind, bentoml_version=get_bentoml_deploy_version(), created_at=str(datetime.utcnow()), )) if bento_service is not None: self.config["metadata"].update({ "service_name": bento_service.name, "service_version": bento_service.version, }) self.config["env"] = bento_service.env.to_dict() self.config['apis'] = _get_apis_list(bento_service) self.config['artifacts'] = _get_artifacts_list(bento_service) def write_to_path(self, path, filename="bentoml.yml"): return self._yaml.dump(self.config, Path(os.path.join(path, filename))) @classmethod def load(cls, filepath): conf = cls() with open(filepath, "rb") as config_file: yml_content = config_file.read() conf.config = conf._yaml.load(yml_content) ver = str(conf["version"]) py_ver = conf.config["env"]["python_version"] if ver != BENTOML_VERSION: msg = ( "Saved BentoService bundle version mismatch: loading BentoService " "bundle create with BentoML version {}, but loading from BentoML " "version {}".format(conf["version"], BENTOML_VERSION)) # If major version is different, then there could be incompatible API # changes. Raise error in this case. if ver.split(".")[0] != BENTOML_VERSION.split(".")[0]: if not BENTOML_VERSION.startswith('0+untagged'): raise BentoMLConfigException(msg) else: logger.warning(msg) else: # Otherwise just show a warning. logger.warning(msg) if py_ver != PYTHON_VERSION: logger.warning( f"Saved BentoService Python version mismatch: loading " f"BentoService bundle created with Python version {py_ver}, " f"but current environment version is {PYTHON_VERSION}.") return conf def get_bento_service_metadata_pb(self): from bentoml.yatai.proto.repository_pb2 import BentoServiceMetadata bento_service_metadata = BentoServiceMetadata() bento_service_metadata.name = self.config["metadata"]["service_name"] bento_service_metadata.version = self.config["metadata"][ "service_version"] bento_service_metadata.created_at.FromDatetime( self.config["metadata"]["created_at"]) if "env" in self.config: if "setup_sh" in self.config["env"]: bento_service_metadata.env.setup_sh = self.config["env"][ "setup_sh"] if "conda_env" in self.config["env"]: bento_service_metadata.env.conda_env = dump_to_yaml_str( self.config["env"]["conda_env"]) if "pip_packages" in self.config["env"]: for pip_package in self.config["env"]["pip_packages"]: bento_service_metadata.env.pip_packages.append(pip_package) if "python_version" in self.config["env"]: bento_service_metadata.env.python_version = self.config["env"][ "python_version"] if "docker_base_image" in self.config["env"]: bento_service_metadata.env.docker_base_image = self.config[ "env"]["docker_base_image"] if "apis" in self.config: for api_config in self.config["apis"]: if 'handler_type' in api_config: # Convert handler type to input type for saved bundle created # before version 0.8.0 input_type = api_config.get('handler_type') elif 'input_type' in api_config: input_type = api_config.get('input_type') else: input_type = "unknown" if 'output_type' in api_config: output_type = api_config.get('output_type') else: output_type = "DefaultOutput" api_metadata = BentoServiceMetadata.BentoServiceApi( name=api_config["name"], docs=api_config["docs"], input_type=input_type, output_type=output_type, ) if "handler_config" in api_config: # Supports viewing API input config info for saved bundle created # before version 0.8.0 for k, v in api_config["handler_config"].items(): if k in {'mb_max_latency', 'mb_max_batch_size'}: setattr(api_metadata, k, v) else: api_metadata.input_config[k] = v else: if 'mb_max_latency' in api_config: api_metadata.mb_max_latency = api_config[ "mb_max_latency"] else: api_metadata.mb_max_latency = DEFAULT_MAX_LATENCY if 'mb_max_batch_size' in api_config: api_metadata.mb_max_batch_size = api_config[ "mb_max_batch_size"] else: api_metadata.mb_max_batch_size = DEFAULT_MAX_BATCH_SIZE if "input_config" in api_config: for k, v in api_config["input_config"].items(): api_metadata.input_config[k] = v if "output_config" in api_config: for k, v in api_config["output_config"].items(): api_metadata.output_config[k] = v api_metadata.batch = api_config.get("batch", False) bento_service_metadata.apis.extend([api_metadata]) if "artifacts" in self.config: for artifact_config in self.config["artifacts"]: artifact_metadata = BentoServiceMetadata.BentoArtifact() if "name" in artifact_config: artifact_metadata.name = artifact_config["name"] if "artifact_type" in artifact_config: artifact_metadata.artifact_type = artifact_config[ "artifact_type"] if "metadata" in artifact_config: if isinstance(artifact_config["metadata"], dict): s = Struct() s.update(artifact_config["metadata"]) artifact_metadata.metadata.CopyFrom(s) else: logger.warning( "Tried to get non-dictionary metadata for artifact " f"{artifact_metadata.name}. Ignoring metadata...") bento_service_metadata.artifacts.extend([artifact_metadata]) return bento_service_metadata def __getitem__(self, item): return self.config[item] def __setitem__(self, key, value): self.config[key] = value def __contains__(self, item): return item in self.config
def deployment_yaml_string_to_pb(deployment_yaml_string): yaml = YAML() deployment_yaml = yaml.load(deployment_yaml_string) return deployment_dict_to_pb(deployment_yaml)