From a4c0f4fd7260caf57732a963a3dacc55bf35c525 Mon Sep 17 00:00:00 2001 From: Christian Kasim Loan Date: Sun, 1 Oct 2023 18:22:56 +0100 Subject: [PATCH] Release/511 (#676) * optimized EMR imports for less chance of import errors * bump versions * update release notes * drop deprecated setup.py * Endpoint tests (#637) * example databricks serve notebooks and cluster creation * updated healthcecks * databricks serve + john snow labs endpoints module * `clean_cluster` and `write_db_credentials` parameters for db cluster creation * databricks serve + john snow labs endpoints docs + typo fixes * databricks serve + john snow labs endpoints docs + typo fixes * updated tests * updated tests * fix block_until_deployed bug * fix block_until_deployed bug * support for GPU and nlu.predict params in endpoints * bump versions * Docs update * update notebooks * endpoint test job generator * improved list_db_runtime_versions * multi cluster testing endpoints * Support for submitting notebook to databricks and various utils & refactor to databricks utils * db test refactor into db_info/endpoint/hdfs/submit tests * get_or_create_test_cluster() and varous more db testing utils, pytest.ini and non-verbose parameterization of tests * add docs for notebook execution --- .../databricks_utils/submit_notebook.png | Bin 0 -> 20549 bytes .../submit_notebook_result.png | Bin 0 -> 111216 bytes docs/en/jsl/databricks_utils.md | 32 ++ docs/en/jsl/jsl_release_notes.md | 62 ++++ .../auto_install/databricks/install_utils.py | 119 ++++++- .../auto_install/databricks/work_utils.py | 159 +++++++-- johnsnowlabs/auto_install/emr/work_utils.py | 12 +- .../health_checks/benchmark_test.py | 1 + .../health_checks/endpoint_test.py | 88 +++-- .../health_checks/generate_endpoint_test.py | 12 + johnsnowlabs/settings.py | 4 +- setup.py | 54 --- tests/databricks/__init__.py | 0 tests/databricks/db_infos_test.py | 47 +++ tests/databricks/db_test_utils.py | 243 +++++++++++++ tests/databricks/endpoint_tests.py | 60 ++++ tests/databricks/hdfs_tests.py | 18 + tests/databricks/sample.ipynb | 58 ++++ tests/databricks/submit_tests.py | 103 ++++++ tests/databricks_tests.py | 328 ------------------ tests/pytest.ini | 4 + 21 files changed, 954 insertions(+), 450 deletions(-) create mode 100644 docs/assets/images/jsl_lib/databricks_utils/submit_notebook.png create mode 100644 docs/assets/images/jsl_lib/databricks_utils/submit_notebook_result.png create mode 100644 johnsnowlabs/auto_install/health_checks/benchmark_test.py create mode 100644 johnsnowlabs/auto_install/health_checks/generate_endpoint_test.py delete mode 100644 setup.py create mode 100644 tests/databricks/__init__.py create mode 100644 tests/databricks/db_infos_test.py create mode 100644 tests/databricks/db_test_utils.py create mode 100644 tests/databricks/endpoint_tests.py create mode 100644 tests/databricks/hdfs_tests.py create mode 100644 tests/databricks/sample.ipynb create mode 100644 tests/databricks/submit_tests.py delete mode 100644 tests/databricks_tests.py create mode 100644 tests/pytest.ini diff --git a/docs/assets/images/jsl_lib/databricks_utils/submit_notebook.png b/docs/assets/images/jsl_lib/databricks_utils/submit_notebook.png new file mode 100644 index 0000000000000000000000000000000000000000..48977c4757c1d5213d0265fd9466e717a12ab19e GIT binary patch literal 20549 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 literal 0 HcmV?d00001 diff --git a/docs/en/jsl/databricks_utils.md b/docs/en/jsl/databricks_utils.md index f88f4a00a5..71cd998f77 100644 --- a/docs/en/jsl/databricks_utils.md +++ b/docs/en/jsl/databricks_utils.md @@ -100,6 +100,38 @@ And after a while you can see the results ![databricks_cluster_submit_raw.png](/assets/images/jsl_lib/databricks_utils/submit_raw_str_result.png) +### Run a local Python Notebook in Databricks + +Provide the path to a notebook on your localhost, it will be copied to HDFS and executed by the Databricks cluster. +You need to provide a destination path to your Workspace, where the notebook will be copied to and you have write access to. +A common pattern that should work is`/Users//test.ipynb` + +```python +local_nb_path = "path/to/my/notebook.ipynb" +remote_dst_path = "/Users/christian@johnsnowlabs.com/test.ipynb" + +# notebook.ipynb will run on databricks, url will be printed +nlp.run_in_databricks( + local_nb_path, + databricks_host=host, + databricks_token=token, + run_name="Notebook Test", + dst_path=remote_dst_path, +) +``` + +This could be your input notebook + +![databricks_cluster_submit_notebook.png](/assets/images/jsl_lib/databricks_utils/submit_notebook.png) + +A URL where you can monitor the run will be printed, which will look like this + +![databricks_cluster_submit_notebook_result.png](/assets/images/jsl_lib/databricks_utils/submit_notebook_result.png) + + + + + ### Run a Python Function in Databricks Define a function, which will be written to a local file, copied to HDFS and executed by the Databricks cluster. diff --git a/docs/en/jsl/jsl_release_notes.md b/docs/en/jsl/jsl_release_notes.md index d9208bf003..a68db83d3b 100644 --- a/docs/en/jsl/jsl_release_notes.md +++ b/docs/en/jsl/jsl_release_notes.md @@ -15,6 +15,27 @@ sidebar: See [Github Releases](https://github.com/JohnSnowLabs/johnsnowlabs/releases) for detailed information on Release History and Features. + + +## 5.1.1 +Release date: 1-10-2023 + +The John Snow Labs 5.1.0 Library released with the following pre-installed and recommended dependencies + + +| Library | Version | +|---------------------------------------------------------------------------------|---------| +| [Visual NLP](https://nlp.johnsnowlabs.com/docs/en/spark_ocr_versions/ocr_release_notes) | `5.0.1` | +| [Enterprise NLP](https://nlp.johnsnowlabs.com/docs/en/licensed_annotators) | `5.1.1` | +| [Finance NLP](https://nlp.johnsnowlabs.com/docs/en/financial_release_notes) | `1.X.X` | +| [Legal NLP](https://nlp.johnsnowlabs.com/docs/en/legal_release_notes) | `1.X.X` | +| [NLU](https://github.com/JohnSnowLabs/nlu/releases) | `5.0.1` | +| [Spark-NLP-Display](https://sparknlp.org/docs/en/display) | `4.4` | +| [Spark-NLP](https://github.com/JohnSnowLabs/spark-nlp/releases/) | `5.1.1` | +| [Pyspark](https://spark.apache.org/docs/latest/api/python/) | `3.1.2` | + + + ## 5.1.0 Release date: 25-09-2023 @@ -91,6 +112,47 @@ The John Snow Labs 5.0.7 Library released with the following pre-installed and r +## 5.0.8 +Release date: 11-09-2023 + +The John Snow Labs 5.0.5 Library released with the following pre-installed and recommended dependencies + + +| Library | Version | +|---------------------------------------------------------------------------------|---------| +| [Visual NLP](https://nlp.johnsnowlabs.com/docs/en/jsl/ocr_release_notes) | `5.0.0` | +| [Enterprise NLP](https://nlp.johnsnowlabs.com/docs/en/jsl/licensed_annotators) | `5.0.2` | +| [Finance NLP](https://nlp.johnsnowlabs.com/docs/en/jsl/financial_release_notes) | `1.X.X` | +| [Legal NLP](https://nlp.johnsnowlabs.com/docs/en/jsl/legal_release_notes) | `1.X.X` | +| [NLU](https://github.com/JohnSnowLabs/nlu/releases) | `5.0.1` | +| [Spark-NLP-Display](https://nlp.johnsnowlabs.com/docs/en/jsl/display) | `4.4` | +| [Spark-NLP](https://github.com/JohnSnowLabs/spark-nlp/releases/) | `5.0.2` | +| [Pyspark](https://spark.apache.org/docs/latest/api/python/) | `3.1.2` | + + + + + +## 5.0.7 +Release date: 3-09-2023 + +The John Snow Labs 5.0.5 Library released with the following pre-installed and recommended dependencies + + +| Library | Version | +|---------------------------------------------------------------------------------|---------| +| [Visual NLP](https://nlp.johnsnowlabs.com/docs/en/jsl/ocr_release_notes) | `5.0.0` | +| [Enterprise NLP](https://nlp.johnsnowlabs.com/docs/en/jsl/licensed_annotators) | `5.0.2` | +| [Finance NLP](https://nlp.johnsnowlabs.com/docs/en/jsl/financial_release_notes) | `1.X.X` | +| [Legal NLP](https://nlp.johnsnowlabs.com/docs/en/jsl/legal_release_notes) | `1.X.X` | +| [NLU](https://github.com/JohnSnowLabs/nlu/releases) | `5.0.0` | +| [Spark-NLP-Display](https://nlp.johnsnowlabs.com/docs/en/jsl/display) | `4.4` | +| [Spark-NLP](https://github.com/JohnSnowLabs/spark-nlp/releases/) | `5.0.2` | +| [Pyspark](https://spark.apache.org/docs/latest/api/python/) | `3.1.2` | + + + + ## 5.0.6 Release date: 03-09-2023 diff --git a/johnsnowlabs/auto_install/databricks/install_utils.py b/johnsnowlabs/auto_install/databricks/install_utils.py index 50f5cb8bcb..c330fc8e7c 100644 --- a/johnsnowlabs/auto_install/databricks/install_utils.py +++ b/johnsnowlabs/auto_install/databricks/install_utils.py @@ -154,7 +154,7 @@ def list_db_runtime_versions(db: DatabricksAPI): # pprint(versions) for version in versions["versions"]: print(version["key"]) - print(version["name"]) + name = version["name"] # version_regex = r'[0-9].[0-9].[0-9]' spark_version = re.findall(r"Apache Spark [0-9].[0-9]", version["name"]) @@ -166,14 +166,15 @@ def list_db_runtime_versions(db: DatabricksAPI): has_gpu = len(re.findall("GPU", version["name"])) > 0 if spark_version: spark_version = spark_version + ".x" - print(LibVersion(spark_version).as_str(), has_gpu, scala_version) - - -def list_clusters(db: DatabricksAPI): - clusters = db.cluster.list_clusters(headers=None) - pprint(clusters) - print(clusters) - return clusters + version = LibVersion(spark_version).as_str() + print( + f"name={name}\n" + f"version={version}\n" + f"has_gpu={has_gpu}\n" + f"scala_version={scala_version}\n" + f"spark_version={spark_version}\n" + f"{'=' * 25}" + ) def list_cluster_lib_status(db: DatabricksAPI, cluster_id: str): @@ -388,13 +389,23 @@ def copy_lib_to_dbfs_cluster( return copy_from_local_to_hdfs(db, local_path=local_path, dbfs_path=dbfs_path) -def wait_till_cluster_running(db: DatabricksAPI, cluster_id: str): +def wait_till_cluster_running(db: DatabricksAPI, cluster_id: str, timeout=900): # https://docs.databricks.com/dev-tools/api/latest/clusters.html#clusterclusterstate import time - while 1: + start_time = time.time() + + while True: + elapsed_time = time.time() - start_time + if elapsed_time > timeout: + print( + "Timeout reached while waiting for the cluster to be running. Check cluster UI." + ) + return False + time.sleep(5) status = DatabricksClusterStates(db.cluster.get_cluster(cluster_id)["state"]) + if status == DatabricksClusterStates.RUNNING: return True elif status in [ @@ -414,3 +425,89 @@ def wait_till_cluster_running(db: DatabricksAPI, cluster_id: str): def restart_cluster(db: DatabricksAPI, cluster_id: str): db.cluster.restart_cluster(cluster_id=cluster_id) + + +#### +def list_clusters(db: DatabricksAPI): + """Lists all clusters.""" + clusters = db.cluster.list_clusters() + return clusters + + +def cluster_exist_with_name_and_runtime( + db: DatabricksAPI, name: str, runtime: str +) -> bool: + """Checks if a cluster with a specific name and runtime exists.""" + clusters = list_clusters(db) + for cluster in clusters.get("clusters", []): + if cluster["cluster_name"] == name and runtime in cluster["spark_version"]: + return True + return False + + +def does_cluster_exist_with_id(db: DatabricksAPI, cluster_id: str) -> bool: + """Checks if a cluster with a specific ID exists.""" + try: + cluster_info = db.cluster.get_cluster(cluster_id=cluster_id) + if cluster_info and "cluster_id" in cluster_info: + return True + except Exception as e: + # Handle specific exceptions based on the Databricks API error responses + pass + return False + + +def get_cluster_id(db: DatabricksAPI, cluster_name: str, runtime: str) -> str: + """ + Retrieves the cluster ID based on the cluster name and runtime. + + If there are multiple candidates: + - Returns any that's in the 'RUNNING' state if there is one. + - If none are in the 'RUNNING' state, returns any. + """ + clusters = list_clusters(db) + running_clusters = [] + other_clusters = [] + + for cluster in clusters.get("clusters", []): + if ( + cluster["cluster_name"] == cluster_name + and runtime in cluster["spark_version"] + ): + if cluster["state"] == "RUNNING": + running_clusters.append(cluster["cluster_id"]) + else: + other_clusters.append(cluster["cluster_id"]) + + if running_clusters: + return running_clusters[0] + elif other_clusters: + return other_clusters[0] + else: + raise Exception( + f"No cluster found with name {cluster_name} and runtime {runtime}" + ) + + +def _get_cluster_id(db: DatabricksAPI, cluster_name: str) -> str: + """ + Retrieves the cluster ID based on the cluster name. + """ + clusters = list_clusters(db) + running_clusters = [] + other_clusters = [] + + for cluster in clusters.get("clusters", []): + if cluster["cluster_name"] == cluster_name: + # if cluster["state"] != "TERMINATED": + other_clusters.append(cluster["cluster_id"]) + + if running_clusters: + return running_clusters[0] + elif other_clusters: + return other_clusters[0] + else: + return None # Return None if no cluster found with the given name + + +############# diff --git a/johnsnowlabs/auto_install/databricks/work_utils.py b/johnsnowlabs/auto_install/databricks/work_utils.py index b482323193..e6461bed1e 100644 --- a/johnsnowlabs/auto_install/databricks/work_utils.py +++ b/johnsnowlabs/auto_install/databricks/work_utils.py @@ -1,3 +1,4 @@ +import base64 import inspect import os.path import time @@ -125,6 +126,7 @@ def run_local_py_script_as_task( cluster_id: str = None, run_name: str = None, parameters: List[Any] = None, + dst_path=None, ): """ # https://docs.databricks.com/dev-tools/api/latest/examples.html#jobs-api-examples @@ -135,47 +137,68 @@ def run_local_py_script_as_task( :param run_name: :return: """ - - task_definition = executable_as_script(task_definition) if not run_name: run_name = settings.db_run_name + if not cluster_id: cluster_id = create_cluster(db) - copy_from_local_to_hdfs( - db=db, - local_path=task_definition, - dbfs_path=get_db_path(task_definition), - ) - py_task = dict( - python_file=get_db_path(task_definition), - ) - if parameters: - py_task["parameters"] = parameters - run_id = db.jobs.submit_run( - existing_cluster_id=cluster_id, - spark_python_task=py_task, - run_name=run_name, - # new_cluster=None, - # libraries=None, - # notebook_task=None, - # spark_jar_task=None, - # spark_submit_task=None, - # timeout_seconds=None, - # tasks=None, - # headers=None, - # version=None, - )["run_id"] + if ( + isinstance(task_definition, str) + and os.path.exists(task_definition) + and task_definition.endswith(".ipynb") + ): + # Notebook task, WIP + # raise Exception("Notebook task not supported yet") + if not dst_path: + raise Exception("dst_path must be provided for notebook tasks") + file_name = os.path.split(task_definition)[-1] + run_id = submit_notebook_to_databricks( + db, task_definition, cluster_id, dst_path + ) + + else: + task_definition = executable_as_script(task_definition) + + copy_from_local_to_hdfs( + db=db, + local_path=task_definition, + dbfs_path=get_db_path(task_definition) if not dst_path else dst_path, + ) + py_task = dict( + python_file=get_db_path(task_definition), + ) + if parameters: + py_task["parameters"] = parameters + + run_id = db.jobs.submit_run( + existing_cluster_id=cluster_id, + spark_python_task=py_task, + run_name=run_name, + )["run_id"] print(f"Stated task with run_id={run_id}") return run_id def executable_as_script(py_executable: Union[str, ModuleType, Callable]): - if isinstance(py_executable, str) and os.path.exists(py_executable): + if ( + isinstance(py_executable, str) + and os.path.exists(py_executable) + and py_executable.endswith(".py") + ): print(f"Detected Python Script for Databricks Submission") # Py file, we can just run this return py_executable + if ( + isinstance(py_executable, str) + and os.path.exists(py_executable) + and py_executable.endswith(".ipynb") + ): + print(f"Detected Python Notebook for Databricks Submission") + # Py file, we can just run this + return py_executable if isinstance(py_executable, (str, ModuleType, Callable)): + print(f"Module/Callable for Databricks Submission") # Convert Module/Callable into a script return executable_to_str(py_executable) raise TypeError(f"Invalid Executable Python Task {py_executable}") @@ -225,6 +248,28 @@ def executable_to_str(executable_to_convert: Union[str, ModuleType, Callable]): return str_to_file(inspect.getsource(executable_to_convert), out_path) +def encode_nb(nb_path): + import base64 + + with open(nb_path, "rb") as f: + encoded_nb = base64.b64encode(f.read()).decode("utf-8") + return encoded_nb + + +def import_notebook_to_workspace( + db: DatabricksAPI, + dst_path: str, + local_nb_path: str, +): + db.workspace.import_workspace( + path=dst_path, + language="PYTHON", + format="JUPYTER", + content=encode_nb(local_nb_path), + overwrite=True, + ) + + def checkon_db_task( db: DatabricksAPI, run_id: str = None, @@ -249,14 +294,37 @@ def run_in_databricks( databricks_host: Optional[str] = None, run_name: str = None, block_till_complete=True, + dst_path: str = None, ): + """ + + :param py_script_path: Either + - Path to local python script i.e "path/to/my_script.py" + - Path to local python notebook i.e "path/to/my_notebook.ipynb" + - string with python code i.e. "1+1" + - python module i.e. import my_module + - python function, i.e. def my_func(): return 1+1 + + :param databricks_cluster_id: cluster ID to run your task on. If none provided, a cluster will be created first. + :param databricks_token: databricks access token + :param databricks_host: databricks host + :param run_name: name of the run to generate, if None, a random name will be generated + :param block_till_complete: if True, this function will block until the task is complete + :param dst_path: path to store the python script/notebook. in databricks, mandatory for notebooks. + I.e. /Users//test.ipynb + :return: job_id + """ from johnsnowlabs.auto_install.databricks.install_utils import ( get_db_client_for_token, ) db_client = get_db_client_for_token(databricks_host, databricks_token) job_id = run_local_py_script_as_task( - db_client, py_script_path, cluster_id=databricks_cluster_id, run_name=run_name + db_client, + py_script_path, + cluster_id=databricks_cluster_id, + run_name=run_name, + dst_path=dst_path, ) if not block_till_complete: @@ -277,3 +345,38 @@ def run_in_databricks( f"Waiting 30 seconds, job {job_id} is still running, its {job_status['state']}" ) time.sleep(30) + + return job_id + + +def submit_notebook_to_databricks(db, local_nb_path, cluster_id, remote_path): + # Instantiate the DatabricksAPI client + # Read the local notebook content + with open(local_nb_path, "rb") as file: + content = file.read() + + # Base64 encode the content + encoded_content = base64.b64encode(content).decode("utf-8") + + # Define the path on Databricks workspace where the notebook will be uploaded + + # Import the notebook to Databricks workspace + db.workspace.import_workspace( + path=remote_path, + format="JUPYTER", + language="PYTHON", + content=encoded_content, + overwrite=True, + ) + + # Define the notebook task + notebook_task = {"notebook_path": remote_path} + + # Submit the job to run the notebook + response = db.jobs.submit_run( + run_name="Notebook_Run", + existing_cluster_id=cluster_id, + notebook_task=notebook_task, + ) + + return response["run_id"] diff --git a/johnsnowlabs/auto_install/emr/work_utils.py b/johnsnowlabs/auto_install/emr/work_utils.py index 3fa6c34936..9259b0515e 100644 --- a/johnsnowlabs/auto_install/emr/work_utils.py +++ b/johnsnowlabs/auto_install/emr/work_utils.py @@ -2,9 +2,6 @@ from random import randrange from typing import Optional -import boto3 -import botocore - from johnsnowlabs.utils.boto_utils import BotoException from johnsnowlabs.utils.file_utils import path_tail from johnsnowlabs.utils.s3_utils import ( @@ -15,11 +12,13 @@ ) -def create_emr_bucket(boto_session: boto3.Session, bucket=None): +def create_emr_bucket(boto_session: "boto3.Session", bucket=None): """Create a bucket for EMR cluster logs :param boto_session: Boto3 session :param bucket: Bucket name """ + import botocore + try: sts_client = boto_session.client("sts") account_id = sts_client.get_caller_identity()["Account"] @@ -40,7 +39,7 @@ def create_emr_bucket(boto_session: boto3.Session, bucket=None): def run_in_emr( py_script_path: str, cluster_id: str, - boto_session: Optional[boto3.Session] = None, + boto_session: Optional["boto3.Session"] = None, script_bucket: Optional[str] = None, bucket_folder_path: Optional[str] = None, run_name: Optional[str] = None, @@ -56,6 +55,9 @@ def run_in_emr( :param execution_role_arn: IAM role to use for the step :return: Step id """ + import boto3 + import botocore + if not boto_session: boto_session = boto3.Session() if check_if_file_exists_in_s3(py_script_path): diff --git a/johnsnowlabs/auto_install/health_checks/benchmark_test.py b/johnsnowlabs/auto_install/health_checks/benchmark_test.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/johnsnowlabs/auto_install/health_checks/benchmark_test.py @@ -0,0 +1 @@ + diff --git a/johnsnowlabs/auto_install/health_checks/endpoint_test.py b/johnsnowlabs/auto_install/health_checks/endpoint_test.py index 57b3655ce5..7a2f269a57 100644 --- a/johnsnowlabs/auto_install/health_checks/endpoint_test.py +++ b/johnsnowlabs/auto_install/health_checks/endpoint_test.py @@ -1,38 +1,82 @@ -lic = """ -ENDPOINT LICENSE -""" -import mlflow +lic = """ENDPOINT LICENSE""" +model = """MODEL TO TEST""" -from johnsnowlabs.auto_install.databricks.endpoints import * +# def new_req(): +# from mlflow.utils.requirements_utils import _get_pinned_requirement +# from johnsnowlabs import settings +# +# _SPARK_NLP_JSL_WHEEL_URI = ( +# "https://pypi.johnsnowlabs.com/{secret}/spark-nlp-jsl/spark_nlp_jsl-" +# + f"{settings.raw_version_medical}-py3-none-any.whl" +# ) +# +# return [ +# f"johnsnowlabs_for_databricks_by_ckl=={settings.raw_version_jsl_lib}", +# _get_pinned_requirement("pyspark"), +# _SPARK_NLP_JSL_WHEEL_URI.format(secret=os.environ["SECRET"]), +# "pandas<=1.5.3", +# ] +# +# mlflow.johnsnowlabs.get_default_pip_requirements = new_req -def new_req(): - from mlflow.utils.requirements_utils import _get_pinned_requirement - from johnsnowlabs import settings - _SPARK_NLP_JSL_WHEEL_URI = ( - "https://pypi.johnsnowlabs.com/{secret}/spark-nlp-jsl/spark_nlp_jsl-" - + f"{settings.raw_version_medical}-py3-none-any.whl" - ) +def print_query_df(query_df): + print(query_df) + for c in query_df.columns: + print(query_df[c]) + + +def find_writable_directory(): + import os - return [ - f"johnsnowlabs_for_databricks_by_ckl=={settings.raw_version_jsl_lib}", - _get_pinned_requirement("pyspark"), - _SPARK_NLP_JSL_WHEEL_URI.format(secret=os.environ["SECRET"]), - "pandas<=1.5.3", - ] + # TODO this does not work + # List of directories to check + candidate_dirs = [os.getcwd()] + for dir_path in candidate_dirs: + if os.access(dir_path, os.W_OK): + print(f"Found writable directory: {dir_path}") + return dir_path + print("Could not find a writable directory") + return None -mlflow.johnsnowlabs.get_default_pip_requirements = new_req + +# mlflow.set_experiment(find_writable_directory()) def run_test(): - mlflow.set_experiment("/Users/christian@johnsnowlabs.com/my-experiment") + import mlflow + import os + from johnsnowlabs.auto_install.databricks.endpoints import ( + query_and_deploy_if_missing, + delete_endpoint, + nlu_name_to_endpoint, + ) + + # mlflow.set_experiment(find_writable_directory()) + mlflow.set_experiment("/Users/christian@johnsnowlabs.com/my-experiment123") os.environ["JOHNSNOWLABS_LICENSE_JSON_FOR_CONTAINER"] = lic os.environ["JOHNSNOWLABS_LICENSE_JSON"] = lic - res = query_and_deploy_if_missing("tokenize", "Hello World", True, True) - print(res) + # 1) one query to construct endpoint and run with default output level + print(query_and_deploy_if_missing(model, "Hello World", True, True)) + + # # 2) One query for every output level + # for o in ["token", "sentence", "document"]: + # print_query_df( + # query_and_deploy_if_missing(model, "Hello World", output_level=o) + # ) + + # 3) cleanup + print(f"Deleting: {nlu_name_to_endpoint(model)}") + delete_endpoint( + nlu_name_to_endpoint(model), + host=os.environ["DATABRICKS_HOST"], + token=os.environ["DATABRICKS_TOKEN"], + ) if __name__ == "__main__": + dbutils.library.installPyPI("mlflow_by_johnsnowlabs") # , version="2.10.0") + dbutils.library.installPyPI("pandas", version="1.5.0") run_test() diff --git a/johnsnowlabs/auto_install/health_checks/generate_endpoint_test.py b/johnsnowlabs/auto_install/health_checks/generate_endpoint_test.py new file mode 100644 index 0000000000..7557122848 --- /dev/null +++ b/johnsnowlabs/auto_install/health_checks/generate_endpoint_test.py @@ -0,0 +1,12 @@ +import inspect + +import johnsnowlabs.auto_install.health_checks.endpoint_test as endp_test + + +def generate_endpoint_test(model, lic): + # read source of endpoint_test.py and replace placeholders with actual values and return new source code + return ( + inspect.getsource(endp_test) + .replace("ENDPOINT LICENSE", lic) + .replace("MODEL TO TEST", model) + ) diff --git a/johnsnowlabs/settings.py b/johnsnowlabs/settings.py index 5f831a5801..8a38bbe04e 100644 --- a/johnsnowlabs/settings.py +++ b/johnsnowlabs/settings.py @@ -9,8 +9,8 @@ ) # These versions are used for auto-installs and version checks -raw_version_jsl_lib = "5.1.0" -raw_version_nlp = "5.1.0" +raw_version_jsl_lib = "5.1.1" +raw_version_nlp = "5.1.1" raw_version_nlu = "5.0.1" diff --git a/setup.py b/setup.py deleted file mode 100644 index 41084ab34a..0000000000 --- a/setup.py +++ /dev/null @@ -1,54 +0,0 @@ -from codecs import open -from os import path - -from setuptools import find_packages, setup - -import johnsnowlabs.settings - -here = path.abspath(path.dirname(__file__)) - -with open(path.join(here, "README.md"), encoding="utf-8") as f: - long_description = f.read() - -REQUIRED_PKGS = [ - f"pyspark=={johnsnowlabs.settings.raw_version_pyspark}", - f"spark-nlp=={johnsnowlabs.settings.raw_version_nlp}", - f"nlu=={johnsnowlabs.settings.raw_version_nlu}", - f"spark-nlp-display=={johnsnowlabs.settings.raw_version_nlp_display}", - "numpy", - "dataclasses", - "requests", - "databricks-api", - f"pydantic=={johnsnowlabs.settings.raw_version_pydantic}", - "colorama", - "boto3" -] - -setup( - version=johnsnowlabs.settings.raw_version_jsl_lib, - # name="johnsnowlabs_for_databricks", - name="johnsnowlabs", - description="The John Snow Labs Library gives you access to all of John Snow Labs Enterprise And Open Source products in an easy and simple manner. Access 10000+ state-of-the-art NLP and OCR models for " - "Finance, Legal and Medical domains. Easily scalable to Spark Cluster ", - long_description=long_description, - install_requires=REQUIRED_PKGS, - long_description_content_type="text/markdown", - url="https://www.johnsnowlabs.com/", - author="John Snow Labs", - author_email="christian@johnsnowlabs.com", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Topic :: Software Development :: Build Tools", - "License :: OSI Approved :: Apache Software License", - # Specify the Python versions you support here. In particular, ensure - # that you indicate whether you support Python 2, Python 3 or both. - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - ], - keywords="Spark NLP OCR Finance Legal Medical John Snow Labs ", - packages=find_packages(exclude=["test*", "tmp*"]), # exclude=['test'] - include_package_data=True, -) diff --git a/tests/databricks/__init__.py b/tests/databricks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/databricks/db_infos_test.py b/tests/databricks/db_infos_test.py new file mode 100644 index 0000000000..ebddc673dd --- /dev/null +++ b/tests/databricks/db_infos_test.py @@ -0,0 +1,47 @@ +from johnsnowlabs.auto_install.databricks.install_utils import * +from johnsnowlabs.auto_install.databricks.install_utils import _get_cluster_id +from tests.databricks.db_test_utils import * +from tests.databricks.db_test_utils import get_or_create_test_cluster + + +@db_cloud_params +def test_list_workspace_infos(creds): + # List infos about the workspace + lic, host, token = creds + db_client = get_db_client_for_token(host, token) + list_db_runtime_versions(db_client) + list_node_types(db_client) + list_clusters(db_client) + + +# Parameterizing over both creds and node_type +@db_cloud_node_params +def test_get_db_cluster_infos(creds, node_type): + # list infos specific to a cluster + lic, host, token = creds + db_client = get_db_client_for_token(host, token) + + cluster_id = get_or_create_test_cluster(creds, node_type, 0) + list_cluster_lib_status(db_client, cluster_id) + pprint(db_client.cluster.get_cluster(cluster_id)) + + # Check cluster name+runtime detected + assert ( + cluster_exist_with_name_and_runtime( + db_client, "TEST_CLUSTER_0", settings.db_spark_version + ) + == True + ) + + # check cluster id exists + assert does_cluster_exist_with_id(db_client, cluster_id) == True + # cluster_id = get_or_create_test_cluster(aws_creds, aws_cpu_node, 0) + + +@db_cloud_params +def test_delete_all_test_clusters(creds): + lic, host, token = creds + db_client = get_db_client_for_token(host, token) + delete_all_test_clusters(db_client) + for i in range(10): + assert _get_cluster_id(db_client, f"TEST_CLUSTER_{i}") is None diff --git a/tests/databricks/db_test_utils.py b/tests/databricks/db_test_utils.py new file mode 100644 index 0000000000..e4967e27e3 --- /dev/null +++ b/tests/databricks/db_test_utils.py @@ -0,0 +1,243 @@ +import queue +import time + +import pytest + +from johnsnowlabs import nlp, settings +from johnsnowlabs.auto_install.databricks.install_utils import ( + cluster_exist_with_name_and_runtime, + get_db_client_for_token, + get_cluster_id, + wait_till_cluster_running, + _get_cluster_id, +) +from johnsnowlabs.auto_install.health_checks.generate_endpoint_test import ( + generate_endpoint_test, +) +from johnsnowlabs.utils.enums import DatabricksClusterStates +from tests.utilsz import secrets as sct + + +@pytest.fixture() +def azure_gpu_node_type(): + return "Standard_NC4as_T4_v3" + + +@pytest.fixture() +def azure_cpu_node(): + return "Standard_DS3_v2" + + +@pytest.fixture() +def aws_cpu_node(): + return "i3.xlarge" + + +@pytest.fixture() +def azure_creds(): + import tests.utilsz.secrets as sct + + lic = sct.db_lic_azure + host = sct.ckl_host_azure + token = sct.ckl_token_azure + return lic, host, token + + +@pytest.fixture() +def azure_trial_creds(): + import tests.utilsz.secrets as sct + + lic = sct.azure_trail_lic2 + host = sct.azure_trail_host2 + token = sct.azure_trail_token2 + return lic, host, token + + +@pytest.fixture() +def aws_creds(): + import tests.utilsz.secrets as sct + + lic = sct.ckl_lic_aws + host = sct.ckl_host_aws + token = sct.ckl_token_aws + return lic, host, token + + +@pytest.fixture +def creds(request): + return request.getfixturevalue(request.param) + + +@pytest.fixture +def node_type(request): + return request.getfixturevalue(request.param) + + +def host_creds(host): + if host == "aws": + return aws_creds() + elif host == "azure": + return azure_creds() + else: + raise Exception("Unknown host") + + +def assert_job_suc(state): + assert state["state"]["result_state"] == "SUCCESS" + + +def run_endpoint_tests(test_cluster_id, host, token, model): + # generate job.py script and submit it + assert_job_suc( + nlp.run_in_databricks( + generate_endpoint_test(model, sct.container_lic_json), + databricks_cluster_id=test_cluster_id, + databricks_host=host, + databricks_token=token, + run_name=f"endpoint_creation_test_run_{model}", + ) + ) + + +def run_cluster_test_suite(test_cluster_id, host, token): + # run test suite again a existing cluster + + # Test modules + import johnsnowlabs.auto_install.health_checks.hc_test as hc_test + import johnsnowlabs.auto_install.health_checks.ocr_test as ocr_test + import johnsnowlabs.auto_install.health_checks.nlp_test as nlp_test + + assert_job_suc( + nlp.run_in_databricks( + nlp_test, + databricks_cluster_id=test_cluster_id, + databricks_host=host, + databricks_token=token, + run_name="nlp_test", + ) + ) + + assert_job_suc( + nlp.run_in_databricks( + hc_test, + databricks_cluster_id=test_cluster_id, + databricks_host=host, + databricks_token=token, + run_name="hc_test", + ) + ) + + assert_job_suc( + nlp.run_in_databricks( + ocr_test, + databricks_cluster_id=test_cluster_id, + databricks_host=host, + databricks_token=token, + run_name="ocr_test", + ) + ) + + +def get_one_model_per_class(): + # todo actually generate from spellbook + return ["sentiment", "ner", "spell", "bert", "elmo", "albert", "roberta"] + + +def delete_all_test_clusters(db_client): + for i in range(10): + while _get_cluster_id(db_client, f"TEST_CLUSTER_{i}"): + cluster_id = _get_cluster_id(db_client, f"TEST_CLUSTER_{i}") + db_client.cluster.permanent_delete_cluster(cluster_id) + print(f"Deleted cluster {cluster_id}") + time.sleep(5) + + +def get_or_create_test_cluster( + creds, node_type, n=0, runtime=None, clean_workspace=False +): + """ + Creates a cluster with name TEST_CLUSTER_{n} and runtime runtime if it doesn't exist. + If it exists, it checks if it's running and if not, it starts it. + "exists" means another cluster with same name and runtime exists. + """ + lic, host, token = creds + # runtime = "9.1.x-scala2.12" + if runtime is None: + runtime = settings.db_spark_version + cluster_name = f"TEST_CLUSTER_{n}" + db_client = get_db_client_for_token(host, token) + + # 1) Check if cluster with name and correct runtime exists and it's running state If not, create it. + if cluster_exist_with_name_and_runtime(db_client, cluster_name, runtime): + cluster_id = get_cluster_id(db_client, cluster_name, runtime) + + status = DatabricksClusterStates( + db_client.cluster.get_cluster( + cluster_id, + )["state"] + ) + + # if status is terminated, try restart it + if status == DatabricksClusterStates.TERMINATED: + db_client.cluster.start_cluster(cluster_id=cluster_id) + wait_till_cluster_running(db_client, cluster_id) + status = DatabricksClusterStates( + db_client.cluster.get_cluster( + cluster_id, + )["state"] + ) + if status == DatabricksClusterStates.RUNNING: + return cluster_id + else: + print( + f"Could not find or start {cluster_name} with runtime {runtime} creating a new one" + ) + + else: + # no cluster exists, create new one + cluster_id = nlp.install( + spark_version=runtime, + json_license_path=lic, + databricks_host=host, + databricks_token=token, + driver_node_type_id=node_type, + node_type_id=node_type, + cluster_name=cluster_name, + clean_cluster=clean_workspace, + block_till_cluster_ready=True, + ) + return cluster_id + + +def subtester_thread(cluster_id, job_que, host, token, results): + while not job_que.empty(): + try: + model = job_que.get_nowait() + except queue.Empty: + return + try: + run_endpoint_tests(cluster_id, host, token, model) + print(f"{model} success!") + results[model] = "SUCCESS" + except Exception as e: + print(f"{model} failed!") + results[model] = f"FAILED: {str(e)}" + + +db_cloud_node_params = pytest.mark.parametrize( + "creds, node_type", + [ + ("aws_creds", "aws_cpu_node"), + ("azure_creds", "azure_cpu_node"), + # ("azure_creds", "azure_gpu_node_type"), + ], + indirect=["creds", "node_type"], +) +db_cloud_params = pytest.mark.parametrize( + "creds", + [ + "aws_creds", + "azure_creds", + ], + indirect=["creds"], +) diff --git a/tests/databricks/endpoint_tests.py b/tests/databricks/endpoint_tests.py new file mode 100644 index 0000000000..f86a84ebc3 --- /dev/null +++ b/tests/databricks/endpoint_tests.py @@ -0,0 +1,60 @@ +from multiprocessing import Queue +from threading import Thread + +from tests.databricks.db_test_utils import * +from tests.databricks.db_test_utils import ( + run_endpoint_tests, + get_one_model_per_class, + get_or_create_test_cluster, + subtester_thread, +) + + +@pytest.mark.skip(reason="WIP") +@db_cloud_node_params +def test_endpoints_multi_cluster(creds, node_type): + n_clusters = 1 + # n_parallel_jobs_per_cluster = 4 # todo add + # 1) Create clusters + cluster_ids = [ + get_or_create_test_cluster(creds, node_type, i) for i in range(n_clusters) + ] + + # 2) Define job-queue + job_que = Queue() + one_model_per_class = get_one_model_per_class() + for model in one_model_per_class: + job_que.put(model) + + # Create a semaphore to limit parallelism per cluster + + # 3) For each cluster, start a tester-thread. + # Threads take jobs from the queue and run them on the cluster till completion. + lic, host, token = aws_creds + threads = [] + results = {} + for cluster_id in cluster_ids: + t = Thread( + target=subtester_thread, args=(cluster_id, job_que, host, token, results) + ) + threads.append(t) + t.start() + + # Wait for all threads to finish + for t in threads: + t.join() + + # 4) Print results + for model, result in results.items(): + print(f"Model {model}: {result}") + + # 5) Delete all clusters + # for cluster_id in cluster_ids: + # delete_cluster(cluster_id) + + +@db_cloud_node_params +def test_endpoint(creds, node_type): + lic, host, token = creds + cluster_id = get_or_create_test_cluster(creds, node_type, 0) + run_endpoint_tests(cluster_id, host, token, "tokenize") diff --git a/tests/databricks/hdfs_tests.py b/tests/databricks/hdfs_tests.py new file mode 100644 index 0000000000..8f50a61d84 --- /dev/null +++ b/tests/databricks/hdfs_tests.py @@ -0,0 +1,18 @@ +import os +from johnsnowlabs.auto_install.databricks.install_utils import * +# fmt: off +from tests.databricks.db_test_utils import aws_creds +# fmt: on + + +def test_hdfs_basic_methods(aws_creds): + lic, host, token = aws_creds + db_client = get_db_client_for_token(host, token) + src_p = os.path.abspath(__file__) + target_p = "/johnsnowlabs/testf" + copy_from_local_to_hdfs(db_client, local_path=src_p, dbfs_path=target_p) + copy_from_local_to_hdfs(db_client, local_path=src_p, dbfs_path=target_p + "2") + copy_from_local_to_hdfs(db_client, local_path=src_p, dbfs_path=target_p + "3") + pprint(dbfs_ls(db_client, "/johnsnowlabs")) + dbfs_rm(db_client, target_p) + pprint(dbfs_ls(db_client, "/johnsnowlabs")) diff --git a/tests/databricks/sample.ipynb b/tests/databricks/sample.ipynb new file mode 100644 index 0000000000..e1199c7702 --- /dev/null +++ b/tests/databricks/sample.ipynb @@ -0,0 +1,58 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from johnsnowlabs import nlp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "pipe = nlp.load('ner')" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "pipe.predict('Peter loves Paris and he lives in London.')" + ], + "metadata": { + "collapsed": false + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/tests/databricks/submit_tests.py b/tests/databricks/submit_tests.py new file mode 100644 index 0000000000..e3d4e6311c --- /dev/null +++ b/tests/databricks/submit_tests.py @@ -0,0 +1,103 @@ +import os.path + +from tests.databricks.db_test_utils import * +from tests.databricks.db_test_utils import ( + assert_job_suc, + run_cluster_test_suite, + get_or_create_test_cluster, +) + + +def nlu_func(): + import nlu + + medical_text = """A 28-year-old female with a history of gestational + diabetes presented with a one-week history of polyuria , + polydipsia , poor appetite , and vomiting .""" + # TODO use licensed models + # df = nlu.load("en.med_ner.diseases").predict(medical_text) + df = nlu.load("tokenize").predict(medical_text) + for c in df.columns: + print(df[c]) + + +@db_cloud_node_params +def test_run_integration_tests(creds, node_type): + lic, host, token = creds + cluster_id = get_or_create_test_cluster(creds, node_type, 0) + run_cluster_test_suite(cluster_id, host, token) + + +@db_cloud_node_params +def test_submit_string_task_to_databricks(creds, node_type): + cluster_id = get_or_create_test_cluster(creds, node_type, 0) + lic, host, token = creds + script = """ +import nlu +print(nlu.load('sentiment').predict('That was easy!')) + """ + assert_job_suc( + nlp.run_in_databricks( + script, + databricks_cluster_id=cluster_id, + databricks_host=host, + databricks_token=token, + run_name="Python Code String Example", + ) + ) + + +@db_cloud_node_params +def test_submit_local_py_file_task_to_databricks(creds, node_type): + cluster_id = get_or_create_test_cluster(creds, node_type, 0) + lic, host, token = creds + # Test script + import johnsnowlabs.auto_install.health_checks.nlp_test as nlp_test + + py_script_file_path = os.path.abspath(nlp_test.__file__) + assert_job_suc( + nlp.run_in_databricks( + py_script_file_path, + databricks_cluster_id=cluster_id, + databricks_host=host, + databricks_token=token, + run_name="Script test", + ) + ) + + +@db_cloud_node_params +def test_submit_function_task_to_databricks(creds, node_type): + cluster_id = get_or_create_test_cluster(creds, node_type, 0) + lic, host, token = creds + # basically reads the function code and submits it to databricks. + # It also copies indentation and comments,watch out for that + assert_job_suc( + nlp.run_in_databricks( + nlu_func, + databricks_cluster_id=cluster_id, + databricks_host=host, + databricks_token=token, + run_name="Function test", + ) + ) + + +@db_cloud_node_params +def test_submit_notebook_task_to_databricks(creds, node_type): + cluster_id = get_or_create_test_cluster(creds, node_type, 0) + lic, host, token = creds + + nb_path = "sample.ipynb" + dst_path = "/Users/christian@johnsnowlabs.com/test.ipynb" + # nb_path = "/home/ckl/Documents/tmp/databricks_endpoints_tutorial_PUBLIC.ipynb" + assert_job_suc( + nlp.run_in_databricks( + nb_path, + databricks_cluster_id=cluster_id, + databricks_host=host, + databricks_token=token, + run_name="Notebook Test", + dst_path=dst_path, + ) + ) diff --git a/tests/databricks_tests.py b/tests/databricks_tests.py deleted file mode 100644 index 81af0dafb1..0000000000 --- a/tests/databricks_tests.py +++ /dev/null @@ -1,328 +0,0 @@ -import pytest - -import tests.utilsz.secrets as sct -from johnsnowlabs import * -from johnsnowlabs.auto_install.databricks.install_utils import * - - -def assert_job_suc(state): - assert state["state"]["result_state"] == "SUCCESS" - - -def run_endpoint_tests(test_cluster_id, host, token): - import johnsnowlabs.auto_install.health_checks.endpoint_test as endp_test - - assert_job_suc( - nlp.run_in_databricks( - endp_test, - databricks_cluster_id=test_cluster_id, - databricks_host=host, - databricks_token=token, - run_name="endpoint_creation_test_run", - ) - ) - - -def run_cluster_test_suite(test_cluster_id, host, token): - # run test suite again a existing cluster - - # Test modules - import johnsnowlabs.auto_install.health_checks.hc_test as hc_test - import johnsnowlabs.auto_install.health_checks.ocr_test as ocr_test - import johnsnowlabs.auto_install.health_checks.nlp_test as nlp_test - - assert_job_suc( - nlp.run_in_databricks( - nlp_test, - databricks_cluster_id=test_cluster_id, - databricks_host=host, - databricks_token=token, - run_name="nlp_test", - ) - ) - - assert_job_suc( - nlp.run_in_databricks( - hc_test, - databricks_cluster_id=test_cluster_id, - databricks_host=host, - databricks_token=token, - run_name="hc_test", - ) - ) - - assert_job_suc( - nlp.run_in_databricks( - ocr_test, - databricks_cluster_id=test_cluster_id, - databricks_host=host, - databricks_token=token, - run_name="ocr_test", - ) - ) - - -@pytest.fixture() -def host_creds(host): - if host == "aws": - return aws_creds() - elif host == "azure": - return azure_creds() - else: - raise Exception("Unknown host") - - -@pytest.fixture() -def azure_gpu_node_type(): - return "Standard_NC4as_T4_v3" - - -@pytest.fixture() -def azure_cpu_node(): - return "Standard_DS3_v2" - - -@pytest.fixture() -def azure_creds(): - lic = sct.db_lic_azure - host = sct.ckl_host_azure - token = sct.ckl_token_azure - return lic, host, token - - -@pytest.fixture() -def azure_trial_creds(): - lic = sct.azure_trail_lic2 - host = sct.azure_trail_host2 - token = sct.azure_trail_token2 - return lic, host, token - - -@pytest.fixture() -def aws_creds(): - lic = sct.ckl_lic_aws - host = sct.ckl_host_aws - token = sct.ckl_token_aws - return lic, host, token - - -@pytest.fixture -def creds(request): - return request.getfixturevalue(request.param) - - -@pytest.mark.parametrize("creds", ["aws_creds", "azure_creds"], indirect=True) -def test_list_db_infos(creds): - # List infos about the workspace - lic, host, token = creds - db_client = get_db_client_for_token(host, token) - list_db_runtime_versions(db_client) - list_node_types(db_client) - list_clusters(db_client) - - -def test_endpoint(azure_trial_creds, azure_cpu_node): - cluster_id = "0831-180255-dki33myd" - lic, host, token = azure_trial_creds - run_endpoint_tests(cluster_id, host, token) - - -def test_get_db_cluster_infos(aws_creds): - # list infos specific to a cluster - lic, host, token = aws_creds - db_client = get_db_client_for_token(host, token) - list_cluster_lib_status(db_client, cluster_id) - pprint(db_client.cluster.get_cluster(cluster_id)) - - -def test_create_fresh_cluster(azure_trial_creds, azure_cpu_node): - # Create a fresh cluster - lic, host, token = azure_trial_creds - # - # nlp.install( - # json_license_path=lic, - # databricks_host=host, - # databricks_token=token, - # hardware_platform="gpu", - # node_type_id="Standard_NC12s_v3", - # driver_node_type_id="Standard_NC6s_v3", - # spark_version="13.1.x-gpu-ml-scala2.12", - # visual=True, - # clean_cluster=False, - # ) - # - instance_type = azure_cpu_node - # lic, host, token = aws_creds - - nlp.install( - json_license_path=lic, - databricks_host=host, - databricks_token=token, - driver_node_type_id=instance_type, - node_type_id=instance_type, - ) - - -def test_create_fresh_cluster_and_run_checks(azure_trial_creds, azure_cpu_node): - instance_type = azure_cpu_node - - lic, host, token = azure_trial_creds - test_cluster_id = nlp.install( - json_license_path=lic, - databricks_host=host, - databricks_token=token, - visual=True, - driver_node_type_id=instance_type, - node_type_id=instance_type, - hardware_platform="gpu", - clean_cluster=False, - ) - # test_cluster_id = "0829-125925-deug1ja1" - run_cluster_test_suite(test_cluster_id, host, token) - # TODO delete cluster - - -def test_create_fresh_cluster2(azure_creds, azure_cpu_node): - instance_type = azure_cpu_node - - parameters = [{}, {}] - - for param in parameters: - lic, host, token = azure_creds - test_cluster_id = nlp.install( - json_license_path=lic, - databricks_host=host, - databricks_token=token, - visual=True, - driver_node_type_id=instance_type, - node_type_id=instance_type, - spark_conf=param, - # hardware_platform="gpu", - ) - # test_cluster_id = "0829-125925-deug1ja1" - start_time = time.time() - - run_cluster_test_suite(test_cluster_id, host, token) - # TODO delete cluster - - end_time = time.time() - print("Time taken: ", end_time - start_time) - - -def test_install_to_databricks(): - db_client = get_db_client_for_token(sct.ckl_host, sct.ckl_token) - cluster_id = nlp.install( - databricks_host=sct.ckl_host, - databricks_token=sct.ckl_token, - ) - - -def test_submit_task_api_to_databricks(): - # Make cluster - # cluster_id = nlp.install(json_license_path=sct.db_lic_by_jsl, databricks_host=sct.ckl_host, - # databricks_token=sct.ckl_token) - cluster_id = "1006-050402-4nsqdu8h" - - # Test script - py_script = "/home/ckl/Documents/freelance/jsl/johnsnowlabs/johnsnowlabs/auto_install/health_checks/hc_test.py" - assert_job_suc( - nlp.run_in_databricks( - py_script, - databricks_cluster_id=cluster_id, - databricks_host=sct.ckl_host, - databricks_token=sct.ckl_token, - run_name="Script test ", - ) - ) - - # Test String - script = """ -import nlu -print(nlu.load('sentiment').predict('That was easy!')) - """ - assert_job_suc( - nlp.run_in_databricks( - script, - databricks_cluster_id=cluster_id, - databricks_host=sct.ckl_host, - databricks_token=sct.ckl_token, - run_name="Python Code String Example", - ) - ) - - assert_job_suc( - nlp.run_in_databricks( - "print('noice')", - databricks_cluster_id=cluster_id, - databricks_host=sct.ckl_host, - databricks_token=sct.ckl_token, - run_name="Code String test 2", - ) - ) - - def nlu_func(): - import nlu - - medical_text = """A 28-year-old female with a history of gestational - diabetes presented with a one-week history of polyuria , - polydipsia , poor appetite , and vomiting .""" - df = nlu.load("en.med_ner.diseases").predict(medical_text) - for c in df.columns: - print(df[c]) - - assert_job_suc( - nlp.run_in_databricks( - nlu_func, - databricks_cluster_id=cluster_id, - databricks_host=sct.ckl_host, - databricks_token=sct.ckl_token, - run_name="Function test", - ) - ) - - # Test String - script = """ -import nlu -print(nlu.load('sentiment').predict('That was easy!')) - """ - assert_job_suc( - nlp.run_in_databricks( - script, - databricks_cluster_id=cluster_id, - databricks_host=sct.ckl_host, - databricks_token=sct.ckl_token, - run_name="Python Code String Example", - ) - ) - - # Test Function - def sample_submit_func(): - print("Test Function") - - assert_job_suc( - nlp.run_in_databricks( - sample_submit_func, - databricks_cluster_id=cluster_id, - databricks_host=sct.ckl_host, - databricks_token=sct.ckl_token, - run_name="Function test", - ) - ) - - run_cluster_test_suite(cluster_id, sct.ckl_host, sct.ckl_token) - - -def test_hdfs_basic_methods(): - db_client = get_db_client_for_token(sct.ckl_host, sct.ckl_token) - src_p = "/home/ckl/old_home/ckl/Documents/freelance/johnsnowlabs_lib/setup.py" - target_p = "/johnsnowlabs/testf" - copy_from_local_to_hdfs(db_client, local_path=src_p, dbfs_path=target_p) - copy_from_local_to_hdfs(db_client, local_path=src_p, dbfs_path=target_p + "2") - copy_from_local_to_hdfs(db_client, local_path=src_p, dbfs_path=target_p + "3") - pprint(dbfs_ls(db_client, "/johnsnowlabs")) - dbfs_rm(db_client, target_p) - pprint(dbfs_ls(db_client, "/johnsnowlabs")) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytest.ini b/tests/pytest.ini new file mode 100644 index 0000000000..2b8ae9edab --- /dev/null +++ b/tests/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +markers = + db_cloud_node_params: marker for parameterizing databricks tests with cloud credentials and node types (azure,aws, gcp) + db_cloud_params: marker for parameterizing databricks tests over all cloud credentials (azure,aws, gcp)