-
Notifications
You must be signed in to change notification settings - Fork 835
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
Sherif akoush issue/2621/refactor env var retrieval #3420
Changes from 11 commits
c4eb21b
10f17d2
0ad71cb
245efca
b465820
07a58ea
acd5346
0c93773
4f57127
e6c3ceb
c20a204
43e17f2
c1b1001
476a7dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
""" | ||
Utilities to deal with Environment variables | ||
""" | ||
import json | ||
import os | ||
|
||
ENV_MODEL_NAME = "PREDICTIVE_UNIT_ID" | ||
ENV_MODEL_IMAGE = "PREDICTIVE_UNIT_IMAGE" | ||
ENV_SELDON_DEPLOYMENT_NAME = "SELDON_DEPLOYMENT_ID" | ||
ENV_PREDICTOR_NAME = "PREDICTOR_ID" | ||
ENV_PREDICTOR_LABELS = "PREDICTOR_LABELS" | ||
NONIMPLEMENTED_MSG = "NOT_IMPLEMENTED" | ||
NONIMPLEMENTED_IMAGE_MSG = f"{NONIMPLEMENTED_MSG}:{NONIMPLEMENTED_MSG}" | ||
|
||
|
||
def get_predictior_version(default_str: str = NONIMPLEMENTED_MSG) -> str: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice catch |
||
return json.loads(os.environ.get(ENV_PREDICTOR_LABELS, "{}")).get( | ||
"version", default_str | ||
) | ||
|
||
|
||
def get_predictor_name(default_str: str = NONIMPLEMENTED_MSG) -> str: | ||
return os.environ.get(ENV_PREDICTOR_NAME, default_str) | ||
|
||
|
||
def get_deployment_name(default_str: str = NONIMPLEMENTED_MSG) -> str: | ||
return os.environ.get(ENV_SELDON_DEPLOYMENT_NAME, default_str) | ||
|
||
|
||
def get_model_name(default_str: str = NONIMPLEMENTED_MSG) -> str: | ||
return os.environ.get(ENV_MODEL_NAME, default_str) | ||
|
||
|
||
def get_image_name(default_str: str = NONIMPLEMENTED_IMAGE_MSG) -> str: | ||
return os.environ.get(ENV_MODEL_IMAGE, default_str) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
import json | ||
import logging | ||
import os | ||
from multiprocessing import Manager | ||
|
@@ -14,15 +13,15 @@ | |
) | ||
from prometheus_client.utils import floatToGoString | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
NONIMPLEMENTED_MSG = "NOT_IMPLEMENTED" | ||
from seldon_core.env_utils import ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could just (if we put this into
and then use Though both works and and usually over-import so does not require a change now. |
||
get_deployment_name, | ||
get_image_name, | ||
get_model_name, | ||
get_predictior_version, | ||
get_predictor_name, | ||
) | ||
|
||
ENV_SELDON_DEPLOYMENT_NAME = "SELDON_DEPLOYMENT_ID" | ||
ENV_MODEL_NAME = "PREDICTIVE_UNIT_ID" | ||
ENV_MODEL_IMAGE = "PREDICTIVE_UNIT_IMAGE" | ||
ENV_PREDICTOR_NAME = "PREDICTOR_ID" | ||
ENV_PREDICTOR_LABELS = "PREDICTOR_LABELS" | ||
logger = logging.getLogger(__name__) | ||
|
||
FEEDBACK_KEY = "seldon_api_model_feedback" | ||
FEEDBACK_REWARD_KEY = "seldon_api_model_feedback_reward" | ||
|
@@ -53,22 +52,18 @@ def split_image_tag(tag: str) -> Tuple[str]: | |
|
||
|
||
# Development placeholder | ||
image = os.environ.get(ENV_MODEL_IMAGE, f"{NONIMPLEMENTED_MSG}:{NONIMPLEMENTED_MSG}") | ||
image = get_image_name() | ||
model_image, model_version = split_image_tag(image) | ||
predictor_version = json.loads(os.environ.get(ENV_PREDICTOR_LABELS, "{}")).get( | ||
"version", f"{NONIMPLEMENTED_MSG}" | ||
) | ||
predictor_version = get_predictior_version() | ||
|
||
legacy_mode = os.environ.get("SELDON_EXECUTOR_ENABLED", "true").lower() == "false" | ||
|
||
DEFAULT_LABELS = { | ||
"deployment_name": os.environ.get( | ||
ENV_SELDON_DEPLOYMENT_NAME, f"{NONIMPLEMENTED_MSG}" | ||
), | ||
"model_name": os.environ.get(ENV_MODEL_NAME, f"{NONIMPLEMENTED_MSG}"), | ||
"deployment_name": get_deployment_name(), | ||
"model_name": get_model_name(), | ||
"model_image": model_image, | ||
"model_version": model_version, | ||
"predictor_name": os.environ.get(ENV_PREDICTOR_NAME, f"{NONIMPLEMENTED_MSG}"), | ||
"predictor_name": get_predictor_name(), | ||
"predictor_version": predictor_version, | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,14 @@ | |
from google.protobuf.json_format import MessageToDict, ParseDict | ||
from google.protobuf.struct_pb2 import ListValue | ||
|
||
from seldon_core.env_utils import ( | ||
ENV_MODEL_IMAGE, | ||
ENV_MODEL_NAME, | ||
NONIMPLEMENTED_IMAGE_MSG, | ||
NONIMPLEMENTED_MSG, | ||
get_image_name, | ||
get_model_name, | ||
) | ||
from seldon_core.flask_utils import SeldonMicroserviceException | ||
from seldon_core.imports_helper import _TF_PRESENT | ||
from seldon_core.proto import prediction_pb2 | ||
|
@@ -27,19 +35,14 @@ | |
logger = logging.getLogger(__name__) | ||
|
||
|
||
ENV_MODEL_NAME = "PREDICTIVE_UNIT_ID" | ||
ENV_MODEL_IMAGE = "PREDICTIVE_UNIT_IMAGE" | ||
NONIMPLEMENTED_MSG = "NOT_IMPLEMENTED" | ||
|
||
model_name = os.environ.get(ENV_MODEL_NAME, f"{NONIMPLEMENTED_MSG}") | ||
image_name = os.environ.get( | ||
ENV_MODEL_IMAGE, f"{NONIMPLEMENTED_MSG}:{NONIMPLEMENTED_MSG}" | ||
) | ||
|
||
|
||
def get_request_path(): | ||
model_name = get_model_name() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Conscious that it falls slightly outside the scope of this PR, but it would make sense to return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think some of these are sometimes used for a key in a dictionary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually... it can. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it can but if we read it back it will be "None" and not |
||
if model_name == NONIMPLEMENTED_MSG: | ||
return {} | ||
image_name = get_image_name() | ||
assert ( | ||
RafalSkolasinski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
image_name != NONIMPLEMENTED_IMAGE_MSG | ||
), f"Both {ENV_MODEL_NAME} and {ENV_MODEL_IMAGE} have to be set" | ||
return {model_name: image_name} | ||
|
||
|
||
|
@@ -164,6 +167,7 @@ def feedback_to_json(message_proto: prediction_pb2.Feedback) -> Dict: | |
---------- | ||
message_proto | ||
SeldonMessage proto | ||
|
||
Returns | ||
------- | ||
JSON Dict | ||
|
@@ -348,6 +352,7 @@ def array_to_list_value(array: np.ndarray, lv: Optional[ListValue] = None) -> Li | |
|
||
Returns | ||
------- | ||
ListValue protobuf | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice catch 👍 |
||
|
||
""" | ||
if lv is None: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,9 @@ | |
import io | ||
import json | ||
import logging | ||
from unittest import mock | ||
|
||
import numpy as np | ||
import pytest | ||
from google.protobuf import json_format | ||
from PIL import Image | ||
|
||
|
@@ -240,6 +240,22 @@ def send_feedback_grpc(self, request): | |
logging.info("Feedback called") | ||
|
||
|
||
@pytest.fixture(name="mock_get_model_name") | ||
def fixture_get_model_name(mocker): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make more sense to mock the environment variables rather than the methods? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having said that, I just realised that you are testing the environment as well down below so maybe you're right in that it makes more sense to mock only the methods here. |
||
return mocker.patch( | ||
"seldon_core.utils.get_model_name", autospec=True, return_value="my-test-model" | ||
) | ||
|
||
|
||
@pytest.fixture(name="mock_get_image_name") | ||
def fixture_get_image_name(mocker): | ||
return mocker.patch( | ||
"seldon_core.utils.get_image_name", | ||
autospec=True, | ||
return_value="my-test-model-image", | ||
) | ||
|
||
|
||
def test_model_ok(): | ||
user_object = UserObject() | ||
seldon_metrics = SeldonMetrics() | ||
|
@@ -311,9 +327,7 @@ def test_model_puid_ok(): | |
assert j["meta"]["puid"] == "123" | ||
|
||
|
||
@mock.patch("seldon_core.utils.model_name", "my-test-model") | ||
@mock.patch("seldon_core.utils.image_name", "my-test-model-image") | ||
def test_requestPath_ok(): | ||
def test_requestPath_ok(mock_get_model_name, mock_get_image_name): | ||
user_object = UserObject() | ||
seldon_metrics = SeldonMetrics() | ||
app = get_rest_microservice(user_object, seldon_metrics) | ||
|
@@ -327,9 +341,7 @@ def test_requestPath_ok(): | |
assert j["meta"]["requestPath"] == {"my-test-model": "my-test-model-image"} | ||
|
||
|
||
@mock.patch("seldon_core.utils.model_name", "my-test-model") | ||
@mock.patch("seldon_core.utils.image_name", "my-test-model-image") | ||
def test_requestPath_2nd_node_ok(): | ||
def test_requestPath_2nd_node_ok(mock_get_model_name, mock_get_image_name): | ||
user_object = UserObject() | ||
seldon_metrics = SeldonMetrics() | ||
app = get_rest_microservice(user_object, seldon_metrics) | ||
|
@@ -346,9 +358,7 @@ def test_requestPath_2nd_node_ok(): | |
} | ||
|
||
|
||
@mock.patch("seldon_core.utils.model_name", "my-test-model") | ||
@mock.patch("seldon_core.utils.image_name", "my-test-model-image") | ||
def test_proto_requestPath_ok(): | ||
def test_proto_requestPath_ok(mock_get_model_name, mock_get_image_name): | ||
user_object = UserObject() | ||
seldon_metrics = SeldonMetrics() | ||
app = SeldonModelGRPC(user_object, seldon_metrics) | ||
|
@@ -366,9 +376,7 @@ def test_proto_requestPath_ok(): | |
assert j["meta"]["requestPath"] == {"my-test-model": "my-test-model-image"} | ||
|
||
|
||
@mock.patch("seldon_core.utils.model_name", "my-test-model") | ||
@mock.patch("seldon_core.utils.image_name", "my-test-model-image") | ||
def test_proto_requestPath_2nd_node_ok(): | ||
def test_proto_requestPath_2nd_node_ok(mock_get_model_name, mock_get_image_name): | ||
user_object = UserObject() | ||
seldon_metrics = SeldonMetrics() | ||
app = SeldonModelGRPC(user_object, seldon_metrics) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we just have these in the utils.py? Is there a particular reason to have a separate utils file/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seems to be some circular dependencies if we do that
I have not investigated a lot into that though. I think that having a separate module to have all env variables handling is ok. isnt it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's already an improvement to have them in one place so if there is no obvious solution to circular dependency let's leave it as it is 👍