This repository was archived by the owner on Apr 15, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Automate Model Examples and remove last references to Scikit-Learn v0.23.2 #146
Open
laguirre-cs
wants to merge
17
commits into
pre-release
Choose a base branch
from
4812-upgrade-scikit-models
base: pre-release
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
0108361
Add unit tests for each model example and logic in build.sh to run th…
laguirre-cs ec6293a
Update model README and notebook README(s) to for currency - add miss…
laguirre-cs c9742b2
Replace scikit_0.23 with python in Scan Manager Config and add curren…
laguirre-cs c92c0b8
Fix broken link in azureml notebook
laguirre-cs 427818f
Bump scan duration time to 2 hours for Fast Exp. Precalculate that ti…
laguirre-cs 7a58d8c
Restructure run_test.sh script for Containerized Model Examples to ad…
laguirre-cs 4697423
Run base tests for containerized model examples in pipeline
laguirre-cs f56f089
Renamed scikit_0.23_deployment.yml to python_deployment.yml
laguirre-cs 9376623
Update precalculate timeout to be 3 hours and reduce scan timeout to …
laguirre-cs 60d472e
Move minio check to only happen when running minikube examples
laguirre-cs 6d900e3
Remove call to sh when calling generate.sh script - use bash instead …
laguirre-cs 5184d6f
Install Model requirements before running containerized model examples
laguirre-cs 7595f0c
Test containerized models first
laguirre-cs a06d945
Consolidate model dependency installation
laguirre-cs aad0d3f
Skip containerized model test script in pipeline - cannot run docker
laguirre-cs 74c5211
Pin sklearn-pandas & xgboost versions - and be sure to activate conda…
laguirre-cs 50649b9
Separate install of ML libraries for Models from packaging related Mo…
laguirre-cs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| import time | ||
| import contextlib | ||
| import subprocess | ||
| import tempfile | ||
| import unittest | ||
| from typing import Optional, Sequence | ||
|
|
||
|
|
||
| def capture_err_and_out(stderr, stdout): | ||
| if stderr is not None: | ||
| print("\n---------------------- (main) stderr: ----------------------") | ||
| print(stderr, end="") | ||
| print("\n------------------------------------------------------------\n") | ||
| if stdout is not None: | ||
| print("\n---------------------- (main) stdout: ----------------------") | ||
| print(stdout, end="") | ||
| print("\n------------------------------------------------------------") | ||
|
|
||
|
|
||
| def capture_output(stdout, stderr, limit=100): | ||
| count = 0 | ||
| print("\n---------------------- (service) stdout: ----------------------\n") | ||
| with open(stdout, 'r+') as f: | ||
| for line in f: | ||
| if count > limit: | ||
| break | ||
| print(line, end="") | ||
| limit += 1 | ||
| print("\n------------------------------------------------------------\n") | ||
| print() | ||
|
|
||
| count = 0 | ||
| print("\n---------------------- (service) stderr: ----------------------\n") | ||
| with open(stderr, 'r+') as f: | ||
| for line in f: | ||
| if count > limit: | ||
| break | ||
| print(line, end="") | ||
| limit += 1 | ||
| print("\n------------------------------------------------------------\n") | ||
|
|
||
|
|
||
| class ModelTest(unittest.TestCase): | ||
| """Base class for testing Certifai Prediction Service Examples. Each example will typically include multiple | ||
| scenarios where: | ||
|
|
||
| 1) a flask server is launched as a background process (via the Certifai Model SDK) | ||
| 2) a Certifai Scan is launched (or just a plain Python script) is launched in the foreground that calls (1) | ||
|
|
||
| Each process that is launched in the foreground, (2), is expected to complete with a 0 exit code. Each process | ||
| launched in the background (1) are expected to be run until explicitly killed. | ||
|
|
||
| The following functions should cover scenarios that run plain Python Scripts:: | ||
|
|
||
| run_standalone_python_script(python_script) | ||
| run_python_app_test(model_app, python_script) | ||
|
|
||
| The following functions should cover scenarios that involve running a Certifai Scan:: | ||
|
|
||
| run_model_and_definition_test('app_dtree.py', 'my-definition.yaml') | ||
| run_model_and_scan('app_dtree.py', 'my-definition.yaml') | ||
| run_model_and_explain('app_dtree.py', 'my-definition.yaml', fast=True) | ||
| """ | ||
| SLEEP_TIME = 5 # 5 seconds | ||
| TERMINATION_TIME = 5 # 5 seconds | ||
| DEFAULT_TEST_TIMEOUT = 2 * 60 # 2 minutes | ||
| DEFAULT_SCAN_TIMEOUT = 60 * 60 * 1 # 1 hour | ||
| PRECALCULATE_TIMEOUT = DEFAULT_SCAN_TIMEOUT * 3 # 3 hours | ||
| bg = None | ||
|
|
||
| def _run_in_foreground(self, command: Sequence[str], timeout: Optional[int] = None): | ||
| try: | ||
| # Run process and wait until it completes | ||
| process = subprocess.run(command, shell=False, capture_output=True, timeout=timeout, text=True) | ||
| process.check_returncode() | ||
| except subprocess.TimeoutExpired as te: | ||
| error = f"\nProcess did not finish within expected time (command={te.cmd}, timeout={te.timeout} seconds). Error: {str(te)}" | ||
| capture_err_and_out(te.stderr, te.stdout) | ||
| self.fail(error) | ||
| except subprocess.CalledProcessError as ce: | ||
| error = f"\nProcess finished with non-zero exit code (command={ce.cmd}, code={ce.returncode}). Error: {str(ce)}" | ||
| capture_err_and_out(ce.stderr, ce.stdout) | ||
| self.fail(error) | ||
|
|
||
| @contextlib.contextmanager | ||
| def _run_in_background(self, command: Sequence[str]): | ||
| with tempfile.NamedTemporaryFile(mode='w+') as stdout, tempfile.NamedTemporaryFile(mode='w+') as stderr: | ||
| try: | ||
| p = subprocess.Popen(command, shell=False, stdout=stdout, stderr=stderr, stdin=subprocess.DEVNULL, | ||
| close_fds=True, text=True) | ||
| yield | ||
| except Exception: | ||
| # WARNING: Killing the subprocess may not kill any workers spawned by the process (e.g. gunicorn!) | ||
| p.kill() | ||
| p.wait() | ||
| capture_output(stdout.name, stderr.name) | ||
| raise | ||
| finally: | ||
| # WARNING: Killing the subprocess may not kill any workers spawned by the process (e.g. gunicorn!) | ||
| p.kill() | ||
| p.wait() | ||
|
|
||
| # Outward facing API | ||
|
|
||
| def run_python_app_test(self, model_app: str, test_script: str): | ||
| # Run a Python Model (flask app) in the background, give it a couple seconds to start up, before running test | ||
| with self._run_in_background(["python", model_app]): | ||
| time.sleep(self.SLEEP_TIME) | ||
| self._run_in_foreground(["python", test_script], timeout=self.DEFAULT_TEST_TIMEOUT) | ||
|
|
||
| def run_standalone_python_script(self, script: str): | ||
| # Run the standalone test script | ||
| self._run_in_foreground(["python", script], timeout=self.DEFAULT_SCAN_TIMEOUT) | ||
|
|
||
| def run_model_and_definition_test(self, model_app: str, definition: str): | ||
| # Run a Python Model (flask app) in the background, give it a couple seconds to start up, before running test | ||
| with self._run_in_background(["python", model_app]): | ||
| time.sleep(self.SLEEP_TIME) | ||
| self._run_in_foreground(["certifai", "definition-test", "-f", definition], timeout=self.DEFAULT_SCAN_TIMEOUT) | ||
|
|
||
| def run_model_and_scan(self, model_app: str, definition: str): | ||
| # Run a Python Model (flask app) in the background, give it a couple seconds to start up, before running test | ||
| with self._run_in_background(f"python {model_app}".split()): | ||
| time.sleep(self.SLEEP_TIME) | ||
| self._run_in_foreground(["certifai", "scan", "-f", definition], timeout=self.DEFAULT_SCAN_TIMEOUT) | ||
|
|
||
| def run_model_and_explain(self, model_app: str, definition: str, fast: bool = False): | ||
| # Run a Python Model (flask app) in the background, give it a couple seconds to start up. | ||
| with self._run_in_background(f"python {model_app}".split()): | ||
| time.sleep(self.SLEEP_TIME) | ||
| if fast: | ||
| # Run the precalculate step prior to the fast explain | ||
| pre_calc_command = ["certifai", "explain", "-f", definition, "--precalculate"] | ||
| self._run_in_foreground(pre_calc_command, timeout=self.PRECALCULATE_TIMEOUT) | ||
| command = ["certifai", "explain", "-f", definition, "--fast"] | ||
| else: | ||
| command = ["certifai", "explain", "-f", definition] | ||
| # Run the explanation scan | ||
| self._run_in_foreground(command, timeout=self.DEFAULT_SCAN_TIMEOUT) | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
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.
The shutdown functionality for the prediction services is kinda wonky right now, especially when testing. It seemed like the best way to handle it was by using Python's subprocess module & just killing the parent process (and for the sake of our examples, the apps run with the dev server so we never risk leaving zombie workers).
/shutdownendpoint only works forgunicornservers (whenproduction=True) - this is a known bug in the Model SDK package./shutdownendpoint - like when running python scans. Nor do we expect all the servers to be run withproduction=Truekill()here will kill the process, but if you are using gunicorn, then the spawned workers are left as zombies