diff --git a/Makefile b/Makefile index 2e67b24f50..f748f6f265 100644 --- a/Makefile +++ b/Makefile @@ -400,6 +400,7 @@ test: clean ## Run mlrun tests --capture=no \ --disable-warnings \ --ignore=tests/integration \ + --ignore=tests/test_notebooks.py \ --ignore=tests/rundb/test_httpdb.py \ -rf \ tests @@ -422,6 +423,7 @@ test-integration: clean ## Run mlrun integration tests --disable-warnings \ -rf \ tests/integration \ + tests/test_notebooks.py \ tests/rundb/test_httpdb.py .PHONY: test-migrations-dockerized diff --git a/mlrun/artifacts/base.py b/mlrun/artifacts/base.py index 59ac203c29..84c5799b0e 100644 --- a/mlrun/artifacts/base.py +++ b/mlrun/artifacts/base.py @@ -104,7 +104,7 @@ def get_store_url(self, with_tag=True, project=None): uri_project = project or self.project uri = "/".join([uri_project, self.db_key]) if with_tag: - uri += "#" + self.tree + uri += ":" + self.tree return get_store_uri(StorePrefix.Artifact, uri) def base_dict(self): diff --git a/mlrun/datastore/store_resources.py b/mlrun/datastore/store_resources.py index 5af39513aa..73d298ab47 100644 --- a/mlrun/datastore/store_resources.py +++ b/mlrun/datastore/store_resources.py @@ -16,7 +16,7 @@ import mlrun from mlrun.config import config -from mlrun.utils.helpers import parse_versioned_object_uri +from mlrun.utils.helpers import parse_versioned_object_uri, parse_artifact_uri from .targets import get_online_target from .v3io import parse_v3io_path from ..utils import DB_SCHEMA, StorePrefix @@ -139,30 +139,23 @@ def get_store_resource(uri, db=None, secrets=None, project=None): return db.get_feature_vector(name, project, tag, uid) elif StorePrefix.is_artifact(kind): - project, name, tag, uid = parse_versioned_object_uri( + project, key, iteration, tag, uid = parse_artifact_uri( uri, project or config.default_project ) - iteration = None - if "/" in name: - loc = uri.find("/") - name = uri[:loc] - try: - iteration = int(uri[loc + 1 :]) - except ValueError: - raise ValueError( - f"illegal store path {uri}, iteration must be integer value" - ) resource = db.read_artifact( - name, project=project, tag=tag or uid, iter=iteration + key, project=project, tag=tag or uid, iter=iteration ) if resource.get("kind", "") == "link": # todo: support other link types (not just iter, move this to the db/api layer resource = db.read_artifact( - name, tag=tag, iter=resource.get("link_iteration", 0), project=project, + key, tag=tag, iter=resource.get("link_iteration", 0), project=project, ) if resource: - return mlrun.artifacts.dict_to_artifact(resource) + # import here to avoid circular imports + from mlrun.artifacts import dict_to_artifact + + return dict_to_artifact(resource) else: stores = mlrun.store_manager.set(secrets, db=db) diff --git a/mlrun/utils/helpers.py b/mlrun/utils/helpers.py index 4ad66e52c4..ab4d4f05bc 100644 --- a/mlrun/utils/helpers.py +++ b/mlrun/utils/helpers.py @@ -385,6 +385,31 @@ def parse_versioned_object_uri(uri, default_project=""): return project, uri, tag, hash_key +def parse_artifact_uri(uri, default_project=""): + uri_pattern = r"^((?P.*)/)?(?P.*?)(\#(?P.*?))?(:(?P.*?))?(@(?P.*))?$" + match = re.match(uri_pattern, uri) + if not match: + raise ValueError( + "Uri not in supported format [/][#][:][@]" + ) + group_dict = match.groupdict() + iteration = group_dict["iteration"] + if iteration is not None: + try: + iteration = int(iteration) + except ValueError: + raise ValueError( + f"illegal store path {uri}, iteration must be integer value" + ) + return ( + group_dict["project"] or default_project, + group_dict["key"], + iteration, + group_dict["tag"], + group_dict["uid"], + ) + + def generate_object_uri(project, name, tag=None, hash_key=None): uri = f"{project}/{name}" diff --git a/setup.cfg b/setup.cfg index d9ab63f8a7..34485221a2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,4 +3,4 @@ test = pytest [tool:pytest] addopts = -v -rf --disable-warnings -python_files = tests/*/test_*.py +python_files = tests/*/test_*.py tests/test_*.py diff --git a/tests/Dockerfile.test-nb b/tests/Dockerfile.test-nb index ace0337b6f..020a4efe9c 100644 --- a/tests/Dockerfile.test-nb +++ b/tests/Dockerfile.test-nb @@ -13,8 +13,9 @@ # limitations under the License. FROM python:3.6-slim +ENV PIP_NO_CACHE_DIR=1 RUN apt-get update && apt-get install -y g++ make git-core -RUN python -m pip install --upgrade --no-cache pip +RUN python -m pip install --upgrade pip~=20.2.0 WORKDIR /mlrun COPY ./requirements.txt ./ @@ -28,3 +29,6 @@ RUN pip install \ COPY . . ENV PYTHONPATH=/mlrun RUN python -m pip install .[complete] +{args} +{pip} +RUN jupyter nbconvert --execute {notebook} --to html diff --git a/tests/clients/__init__.py b/tests/clients/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/test_datastores.py b/tests/test_datastores.py index e8850ccdf6..e50d8d043e 100644 --- a/tests/test_datastores.py +++ b/tests/test_datastores.py @@ -102,41 +102,40 @@ def test_parse_url_preserve_case(): def test_get_store_artifact_url_parsing(): db = Mock() - store_manager = mlrun.datastore.StoreManager(db=db) cases = [ { "url": "store:///artifact_key", "project": "default", "key": "artifact_key", - "tag": "", + "tag": None, "iter": None, }, { "url": "store://project_name/artifact_key", "project": "project_name", "key": "artifact_key", - "tag": "", + "tag": None, "iter": None, }, { - "url": "store://Project_Name/Artifact_Key#ABC", + "url": "store://Project_Name/Artifact_Key@ABC", "project": "Project_Name", "key": "Artifact_Key", "tag": "ABC", "iter": None, }, { - "url": "store://project_name/artifact_key#a5dc8e34a46240bb9a07cd9deb3609c7", + "url": "store://project_name/artifact_key@a5dc8e34a46240bb9a07cd9deb3609c7", "project": "project_name", "key": "artifact_key", "tag": "a5dc8e34a46240bb9a07cd9deb3609c7", "iter": None, }, { - "url": "store://project_name/artifact_key/1", + "url": "store://project_name/artifact_key#1", "project": "project_name", "key": "artifact_key", - "tag": "", + "tag": None, "iter": 1, }, { @@ -147,14 +146,14 @@ def test_get_store_artifact_url_parsing(): "iter": None, }, { - "url": "store:///ArtifacT_key/1:some_Tag", + "url": "store:///ArtifacT_key#1:some_Tag", "project": "default", "key": "ArtifacT_key", "tag": "some_Tag", "iter": 1, }, { - "url": "store:///ArtifacT_key/1#Some_Tag", + "url": "store:///ArtifacT_key#1@Some_Tag", "project": "default", "key": "ArtifacT_key", "tag": "Some_Tag", @@ -183,7 +182,7 @@ def mock_read_artifact(key, tag=None, iter=None, project=""): return {} db.read_artifact = mock_read_artifact - store_manager.get_store_artifact(url) + mlrun.datastore.store_resources.get_store_resource(url, db) @pytest.mark.usefixtures("patch_file_forbidden") diff --git a/tests/test_examples.py b/tests/test_examples.py index 43a16440de..6865f96d9e 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -48,5 +48,5 @@ def test_example(db, fname): "run", path, ] - out = run(cmd, env=env) + out = run(cmd, env=env, cwd=examples_path) assert out.returncode == 0, "bad run" diff --git a/tests/test_kfp.py b/tests/test_kfp.py index da1827ae1e..d43126f265 100644 --- a/tests/test_kfp.py +++ b/tests/test_kfp.py @@ -148,7 +148,7 @@ def _assert_iteration_output_dir_files(output_dir): contents = results_file.read() assert contents == results_body assert os.path.exists(output_dir + "/chart.html") - assert os.path.exists(output_dir + "/mydf.csv") + assert os.path.exists(output_dir + "/mydf.parquet") def _assert_artifacts_dir(artifacts_dir, expected_accuracy, expected_loss): diff --git a/tests/test_log_serialization.py b/tests/test_log_serialization.py index 2a6f8a4676..206a5a52cb 100644 --- a/tests/test_log_serialization.py +++ b/tests/test_log_serialization.py @@ -46,7 +46,7 @@ def my_func(context): "y": [25, 94, 0.1, 57, datetime.datetime(2018, 1, 1)], } df = pd.DataFrame(raw_data, columns=["first_name", "last_name", "x", "y"]) - context.log_dataset("df1", df=df) + context.log_dataset("df1", df=df, format="csv") date_rng = pd.date_range("2018-01-01", periods=4, freq="H") df = pd.DataFrame(date_rng, columns=["date"]) diff --git a/tests/test_main.py b/tests/test_main.py index 37eb19d614..b80071bec4 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -17,7 +17,6 @@ from tests.conftest import ( tests_root_directory, examples_path, - root_path, ) @@ -25,7 +24,7 @@ def exec_main(op, args): cmd = [executable, "-m", "mlrun", op] if args: cmd += args - out = run(cmd, stdout=PIPE, stderr=PIPE, cwd=root_path) + out = run(cmd, stdout=PIPE, stderr=PIPE, cwd=examples_path) if out.returncode != 0: print(out.stderr.decode("utf-8"), file=stderr) raise Exception(out.stderr.decode("utf-8")) diff --git a/tests/test_notebooks.py b/tests/test_notebooks.py index ca8f5ca24a..d0eaefe2c1 100644 --- a/tests/test_notebooks.py +++ b/tests/test_notebooks.py @@ -74,4 +74,4 @@ def test_notebook(notebook): + ["."] ) out = run(cmd, cwd=root) - assert out.returncode == 0, "cannot build" + assert out.returncode == 0, f"Failed building {out.stdout} {out.stderr}" diff --git a/tests/test_remote.py b/tests/test_remote.py deleted file mode 100644 index 9a66b8cff4..0000000000 --- a/tests/test_remote.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright 2018 Iguazio -# -# 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. - -import time -import _thread - -from tests.conftest import out_path, tag_test -from tests.http_srv import create_function -from mlrun import get_or_create_ctx, new_function, RunObject, new_task - - -def myfunction(context, event): - ctx = get_or_create_ctx("nuclio-test", event=event) - p1 = ctx.get_param("p1", 1) - p2 = ctx.get_param("p2", "a-string") - - context.logger.info( - f"Run: {ctx.name} uid={ctx.uid}:{ctx.iteration} Params: p1={p1}, p2={p2}" - ) - - time.sleep(1) - - # log scalar values (KFP metrics) - ctx.log_result("accuracy", p1 * 2) - ctx.log_result("latency", p1 * 3) - - # log various types of artifacts (and set UI viewers) - ctx.log_artifact("test.txt", body=b"abc is 123") - ctx.log_artifact("test.html", body=b" Some HTML ", viewer="web-app") - - context.logger.info("run complete!") - return ctx.to_json() - - -base_spec = new_task(params={"p1": 8}, out_path=out_path) - - -def verify_state(result: RunObject): - state = result.status.state - assert state == "completed", f"wrong state ({state}) {result.status.error}" - - -def test_simple_function(): - # Thread(target=create_function, args=(myfunction, 4444)).start() - _thread.start_new_thread(create_function, (myfunction, 4444)) - time.sleep(2) - - spec = tag_test(base_spec, "simple_function") - result = new_function(command="http://localhost:4444").run(spec) - print(result) - verify_state(result) - - -def test_hyper_function(): - # Thread(target=create_function, args=(myfunction, 4444)) - _thread.start_new_thread(create_function, (myfunction, 4444)) - time.sleep(2) - - spec = tag_test(base_spec, "hyper_function") - spec.spec.hyperparams = {"p1": [1, 2, 3]} - result = new_function(command="http://localhost:4444").run(spec) - print(result) - verify_state(result) diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 913ac7ae38..7861d3f067 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -49,7 +49,9 @@ def test_requirement_specifiers_inconsistencies(): ignored_inconsistencies_map = { # It's ==1.4.1 only in models-gpu because of tensorflow 2.2.0, TF version expected to be changed there soon so # ignoring for now - "scipy": ["~=1.0", "==1.4.1", "~=1.0"] + "scipy": ["~=1.0", "==1.4.1", "~=1.0"], + # It's ok we have 2 different versions cause they are for different python versions + "pandas": ["~=1.2; python_version >= '3.7'", "~=1.0; python_version < '3.7'"], } for ( diff --git a/tests/test_run_local.py b/tests/test_run_local.py index 3f5da6bdfd..df9f29c9c5 100644 --- a/tests/test_run_local.py +++ b/tests/test_run_local.py @@ -46,6 +46,7 @@ def test_run_local_with_uid_does_not_exist(monkeypatch): def mock_getpwuid_raise(*args, **kwargs): raise KeyError("getpwuid(): uid not found: 400") + old_v3io_username = environ.pop("V3IO_USERNAME", None) environ["V3IO_USERNAME"] = "some_user" monkeypatch.setattr(getpass, "getuser", mock_getpwuid_raise) spec = tag_test(base_spec, "test_run_local") @@ -53,6 +54,10 @@ def mock_getpwuid_raise(*args, **kwargs): spec, command=f"{examples_path}/training.py", workdir=examples_path ) verify_state(result) + if old_v3io_username is not None: + environ["V3IO_USERNAME"] = old_v3io_username + else: + del environ["V3IO_USERNAME"] def test_run_local_handler():