Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BentoML configuration base #158

Merged
merged 8 commits into from Jun 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 20 additions & 1 deletion MANIFEST.in
@@ -1,13 +1,32 @@
# Copyright 2019 Atalaya Tech, Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

include README.md
graft docs
include LICENSE

# include ".cfg" files
graft bentoml/config

# Don't include examples, tests directory
prune examples
prune tests
prune docs

# Patterns to exclude from any directory
global-exclude *~
global-exclude *.pyc
global-exclude *.pyo
global-exclude .git
global-exclude .ipynb_checkpoints
global-exclude __pycache__
6 changes: 4 additions & 2 deletions bentoml/__init__.py
Expand Up @@ -17,6 +17,7 @@
from __future__ import print_function

from bentoml import handlers
from bentoml.config import config
from bentoml.version import __version__

from bentoml.service import (
Expand All @@ -32,12 +33,13 @@
__all__ = [
"__version__",
"api",
"artifacts",
"config",
"env",
"ver",
"artifacts",
"BentoService",
"save",
"load",
"handlers",
"metrics",
"BentoService",
]
2 changes: 1 addition & 1 deletion bentoml/archive/loader.py
Expand Up @@ -21,7 +21,7 @@
import tempfile

from bentoml.utils.s3 import is_s3_url, download_from_s3
from bentoml.utils.exceptions import BentoMLException
from bentoml.exceptions import BentoMLException
from bentoml.archive.config import BentoArchiveConfig


Expand Down
2 changes: 1 addition & 1 deletion bentoml/archive/py_module_utils.py
Expand Up @@ -27,7 +27,7 @@
from six import string_types, iteritems

from bentoml.utils import Path
from bentoml.utils.exceptions import BentoMLException
from bentoml.exceptions import BentoMLException


def _get_module_src_file(module):
Expand Down
2 changes: 1 addition & 1 deletion bentoml/cli/__init__.py
Expand Up @@ -28,7 +28,7 @@
from bentoml.cli.click_utils import DefaultCommandGroup, conditional_argument
from bentoml.deployment.serverless import ServerlessDeployment
from bentoml.deployment.sagemaker import SagemakerDeployment
from bentoml.utils.exceptions import BentoMLException
from bentoml.exceptions import BentoMLException

SERVERLESS_PLATFORMS = ["aws-lambda", "aws-lambda-py2", "gcp-function"]

Expand Down
67 changes: 67 additions & 0 deletions bentoml/config/__init__.py
@@ -0,0 +1,67 @@
# Copyright 2019 Atalaya Tech, Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import os
import logging

from bentoml.utils import Path
from bentoml.exceptions import BentoMLConfigException
from bentoml.config.configparser import BentoConfigParser

logger = logging.getLogger(__name__)


def expand_env_var(env_var):
"""Expands potentially nested env var by repeatedly applying `expandvars` and
`expanduser` until interpolation stops having any effect.
"""
if not env_var:
return env_var
while True:
interpolated = os.path.expanduser(os.path.expandvars(str(env_var)))
if interpolated == env_var:
return interpolated
else:
env_var = interpolated


BENTOML_HOME = expand_env_var(os.environ.get("BENTOML_HOME", "~/bentoml"))
try:
Path(BENTOML_HOME).mkdir(exist_ok=True)
except OSError as err:
raise BentoMLConfigException(
"Error creating bentoml home dir '{}': {}".format(BENTOML_HOME, err.strerror)
)

# Default bentoml config comes with the library bentoml/config/default_bentoml.cfg
DEFAULT_CONFIG_FILE = os.path.join(os.path.dirname(__file__), "default_bentoml.cfg")
with open(DEFAULT_CONFIG_FILE, "rb") as f:
DEFAULT_CONFIG = f.read().decode("utf-8")

if "BENTML_CONFIG" in os.environ:
# User local config file for customizing bentoml
BENTOML_CONFIG_FILE = expand_env_var(os.environ.get("BENTML_CONFIG"))
else:
BENTOML_CONFIG_FILE = os.path.join(BENTOML_HOME, "bentoml.cfg")
if not os.path.isfile(BENTOML_CONFIG_FILE):
logger.info("Creating new Bentoml config file: %s", BENTOML_CONFIG_FILE)
with open(BENTOML_CONFIG_FILE, "w") as f:
f.write(DEFAULT_CONFIG)

config = BentoConfigParser(default_config=DEFAULT_CONFIG)
config.read(BENTOML_CONFIG_FILE)
99 changes: 99 additions & 0 deletions bentoml/config/configparser.py
@@ -0,0 +1,99 @@
# Copyright 2019 Atalaya Tech, Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import os
import logging
from collections import OrderedDict

from configparser import ConfigParser

from bentoml.exceptions import BentoMLConfigException

logger = logging.getLogger(__name__)


class BentoConfigParser(ConfigParser):
""" BentoML configuration parser

:param default_config string - serve as default value when conf key not presented in
environment var or user local config file
"""

def __init__(self, default_config, *args, **kwargs):
ConfigParser.__init__(self, *args, **kwargs)

self.bentoml_defaults = ConfigParser(*args, **kwargs)
if default_config is not None:
self.bentoml_defaults.read_string(default_config)

@staticmethod
def _env_var_name(section, key):
return "BENTOML__{}__{}".format(section.upper(), key.upper())

def get(self, section, key, **kwargs):
""" A simple hierachical config access, priority order:
1. environment var
2. user config file
3. bentoml default config file
"""
section = str(section).lower()
key = str(key).lower()

env_var = self._env_var_name(section, key)
if env_var in os.environ:
return os.environ[env_var]

if ConfigParser.has_option(self, section, key):
return ConfigParser.get(self, section, key, **kwargs)

if self.bentoml_defaults.has_option(section, key):
return self.bentoml_defaults.get(section, key, **kwargs)
else:
raise BentoMLConfigException(
"section/key '{}/{}' not found in BentoML config".format(section, key)
)

def as_dict(self, display_source=False):
cfg = {}

def add_config(cfg, source, config):
for section in config:
cfg.setdefault(section, OrderedDict())
for k, val in config.items(section=section, raw=False):
if display_source:
cfg[section][k] = (val, source)
else:
cfg[section][k] = val

add_config(cfg, "default", self.bentoml_defaults)
add_config(cfg, "bentoml.cfg", self)

for ev in os.environ:
if ev.startswith("BENTOML__"):
_, section, key = ev.split("__")
val = os.environ[ev]
if display_source:
val = (val, "env var")
cfg.setdefault(section.lower(), OrderedDict()).update(
{key.lower(): val}
)

return cfg

def __repr__(self):
return "<BentoML config: {}>".format(str(self.as_dict()))
36 changes: 28 additions & 8 deletions bentoml/utils/config.py → bentoml/config/default_bentoml.cfg
Expand Up @@ -12,11 +12,31 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

# env var
# ~/.bentomlrc
# cli args?
# user specified global conf
# This is the default configuration for BentoML. When bentoml is imported, it looks
# for a configuration file at "$BENTOML_HOME/bentoml.cfg".

[core]

logging_level = INFO
logging_config_class =
log_format =

usage_report = true


[apiserver]
default_port = 5000
enable_metrics = true
enable_feedback = true

[cli]
#
#

[tensorflow]
#
#

[pytorch]
#
#
2 changes: 1 addition & 1 deletion bentoml/deployment/sagemaker/__init__.py
Expand Up @@ -31,7 +31,7 @@
from bentoml.deployment.base_deployment import Deployment
from bentoml.deployment.utils import generate_bentoml_deployment_snapshot_path
from bentoml.utils.whichcraft import which
from bentoml.utils.exceptions import BentoMLException
from bentoml.exceptions import BentoMLException
from bentoml.deployment.sagemaker.templates import (
DEFAULT_NGINX_CONFIG,
DEFAULT_WSGI_PY,
Expand Down
2 changes: 1 addition & 1 deletion bentoml/deployment/serverless/__init__.py
Expand Up @@ -28,7 +28,7 @@
from bentoml.utils import Path
from bentoml.utils.tempdir import TempDirectory
from bentoml.utils.whichcraft import which
from bentoml.utils.exceptions import BentoMLException
from bentoml.exceptions import BentoMLException
from bentoml.deployment.base_deployment import Deployment
from bentoml.deployment.serverless.aws_lambda_template import (
create_aws_lambda_bundle,
Expand Down
5 changes: 2 additions & 3 deletions bentoml/deployment/utils.py
Expand Up @@ -20,13 +20,12 @@

from datetime import datetime

from bentoml.utils import Path
from bentoml import config


def generate_bentoml_deployment_snapshot_path(service_name, service_version, platform):
return os.path.join(
str(Path.home()),
".bentoml",
config.BENTOML_HOME,
"deployment-snapshots",
platform,
service_name,
Expand Down
6 changes: 6 additions & 0 deletions bentoml/utils/exceptions.py → bentoml/exceptions.py
Expand Up @@ -22,3 +22,9 @@ class BentoMLException(Exception):
Base class for all BentoML's errors.
Each custom exception should be derived from this class
"""

status_code = 500


class BentoMLConfigException(BentoMLException):
pass
2 changes: 1 addition & 1 deletion bentoml/handlers/image_handler.py
Expand Up @@ -24,7 +24,7 @@
from werkzeug.utils import secure_filename
from flask import Response

from bentoml.utils.exceptions import BentoMLException
from bentoml.exceptions import BentoMLException
from bentoml.handlers.base_handlers import BentoHandler, get_output_str

ACCEPTED_CONTENT_TYPES = ["images/png", "images/jpeg", "images/jpg"]
Expand Down