Exemplo n.º 1
0
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)
Exemplo n.º 2
0
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']
Exemplo n.º 3
0
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')
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
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']
Exemplo n.º 6
0
            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
Exemplo n.º 7
0
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
Exemplo n.º 8
0
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)