From 8e9a8a591c6fb3f7ad9b6e2ad7fc507ba277b4b1 Mon Sep 17 00:00:00 2001 From: Krishna Kumar Date: Fri, 19 Jan 2024 06:45:03 -0600 Subject: [PATCH] Unified designsafe api - dapi --- .github/workflows/pypi.yml | 2 +- README.md | 38 +- {dsjobs => dapi/jobs}/__init__.py | 6 +- {dsjobs => dapi/jobs}/dir.py | 0 {dsjobs => dapi/jobs}/jobs.py | 1 - docs/dapi/dir.html | 193 +++++ docs/dapi/index.html | 60 ++ docs/dapi/jobs.html | 605 ++++++++++++++ docs/dapi/jobs/dir.html | 193 +++++ docs/{dsjobs => dapi/jobs}/index.html | 31 +- docs/dapi/jobs/jobs.html | 605 ++++++++++++++ docs/dsjobs.html | 278 ------ docs/dsjobs/dir.html | 372 -------- docs/dsjobs/jobs.html | 791 ------------------ docs/index.html | 7 - docs/search.js | 46 - .../mpm/papermill-mpm.ipynb | 0 .../mpm/template-mpm-run.ipynb | 6 +- pyproject.toml | 6 +- tests/{ => jobs}/__init__.py | 0 tests/{ => jobs}/test_dir_uri.py | 2 +- tests/{ => jobs}/test_job_gen_jobinfo.py | 2 +- tests/{ => jobs}/test_job_get_archive_path.py | 2 +- tests/{ => jobs}/test_job_runtime.py | 4 +- tests/{ => jobs}/test_job_status.py | 4 +- 25 files changed, 1710 insertions(+), 1544 deletions(-) rename {dsjobs => dapi/jobs}/__init__.py (51%) rename {dsjobs => dapi/jobs}/dir.py (100%) rename {dsjobs => dapi/jobs}/jobs.py (99%) create mode 100644 docs/dapi/dir.html create mode 100644 docs/dapi/index.html create mode 100644 docs/dapi/jobs.html create mode 100644 docs/dapi/jobs/dir.html rename docs/{dsjobs => dapi/jobs}/index.html (81%) create mode 100644 docs/dapi/jobs/jobs.html delete mode 100644 docs/dsjobs.html delete mode 100644 docs/dsjobs/dir.html delete mode 100644 docs/dsjobs/jobs.html delete mode 100644 docs/index.html delete mode 100644 docs/search.js rename {example-notebooks => examples}/mpm/papermill-mpm.ipynb (100%) rename {example-notebooks => examples}/mpm/template-mpm-run.ipynb (98%) rename tests/{ => jobs}/__init__.py (100%) rename tests/{ => jobs}/test_dir_uri.py (98%) rename tests/{ => jobs}/test_job_gen_jobinfo.py (98%) rename tests/{ => jobs}/test_job_get_archive_path.py (96%) rename tests/{ => jobs}/test_job_runtime.py (97%) rename tests/{ => jobs}/test_job_status.py (92%) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 1f55a07..0784ded 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest environment: name: pypi - url: https://pypi.org/p/dsjobs + url: https://pypi.org/p/dapi permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: diff --git a/README.md b/README.md index d82e2eb..352f7e4 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,71 @@ # DesignSafe Jobs -[![build and test](https://github.com/DesignSafe-CI/dsjobs/actions/workflows/build-test.yml/badge.svg)](https://github.com/DesignSafe-CI/dsjobs/actions/workflows/build-test.yml) +[![build and test](https://github.com/DesignSafe-CI/dapi/actions/workflows/build-test.yml/badge.svg)](https://github.com/DesignSafe-CI/dapi/actions/workflows/build-test.yml) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.md) -[![Docs](https://img.shields.io/badge/view-docs-8A2BE2?color=8A2BE2)](https://designsafe-ci.github.io/dsjobs/dsjobs/index.html) +[![Docs](https://img.shields.io/badge/view-docs-8A2BE2?color=8A2BE2)](https://designsafe-ci.github.io/dapi/dapi/index.html) -`dsjobs` is a library that simplifies the process of submitting, running, and monitoring [TAPIS v2 / AgavePy](https://agavepy.readthedocs.io/en/latest/index.html) jobs on [DesignSafe](https://designsafe-ci.org) via [Jupyter Notebooks](https://jupyter.designsafe-ci.org). +`dapi` is a library that simplifies the process of submitting, running, and monitoring [TAPIS v2 / AgavePy](https://agavepy.readthedocs.io/en/latest/index.html) jobs on [DesignSafe](https://designsafe-ci.org) via [Jupyter Notebooks](https://jupyter.designsafe-ci.org). ## Features -* Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. `dsjobs` abstracts away the complexities. +* Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. `dapi` abstracts away the complexities. * Seamless Integration with DesignSafe Jupyter Notebooks: Launch DesignSafe applications directly from the Jupyter environment. ## Installation -Install `dsjobs` via pip +Install `dapi` via pip ```shell -pip3 install dsjobs +pip3 install dapi ``` To install the current development version of the library use: ```shell -pip install git+https://github.com/DesignSafe-CI/dsjobs.git --quiet +pip install git+https://github.com/DesignSafe-CI/dapi.git --quiet ``` ## Example usage: -* [Jupyter Notebook Templates](example-notebooks/template-mpm-run.ipynb) using DSJobs. +* [Jupyter Notebook Templates](example-notebooks/template-mpm-run.ipynb) using dapi. -* View [DSJobs API doc](https://designsafe-ci.github.io/dsjobs/dsjobs/index.html) +* View [dapi API doc](https://designsafe-ci.github.io/dapi/dapi/index.html) On [DesignSafe Jupyter](https://jupyter.designsafe-ci.org/): -Install the latest version of `dsjobs` and restart the kernel (Kernel >> Restart Kernel): +Install the latest version of `dapi` and restart the kernel (Kernel >> Restart Kernel): ```python # Remove any previous installations -!pip uninstall dsjobs -y +!pip uninstall dapi -y # Install -!pip install dsjobs --quiet +!pip install dapi --quiet ``` -* Import `dsjobs` library +* Import `dapi` library ```python -import dsjobs as ds +import dapi as ds ``` -* To list all functions in `dsjobs` +* To list all functions in `dapi` ```python dir(ds) ``` ## Documentation -View [DSJobs API doc](https://designsafe-ci.github.io/dsjobs/dsjobs/index.html) +View [dapi API doc](https://designsafe-ci.github.io/dapi/dapi/index.html) To generate API docs: ``` -pdoc --html --output-dir docs dsjobs --force +pdoc --html --output-dir docs dapi --force ``` ## Support -For any questions, issues, or feedback submit an [issue](https://github.com/DesignSafe-CI/dsjobs/issues/new) +For any questions, issues, or feedback submit an [issue](https://github.com/DesignSafe-CI/dapi/issues/new) ## Development @@ -84,7 +84,7 @@ poetry run pytest -v ## License -`dsjobs` is licensed under the [MIT License](LICENSE.md). +`dapi` is licensed under the [MIT License](LICENSE.md). ## Authors diff --git a/dsjobs/__init__.py b/dapi/jobs/__init__.py similarity index 51% rename from dsjobs/__init__.py rename to dapi/jobs/__init__.py index 637af6e..9c59b84 100644 --- a/dsjobs/__init__.py +++ b/dapi/jobs/__init__.py @@ -1,17 +1,17 @@ """ -`dsjobs` is a library that simplifies the process of submitting, running, and monitoring [TAPIS v2 / AgavePy](https://agavepy.readthedocs.io/en/latest/index.html) jobs on [DesignSafe](https://designsafe-ci.org) via [Jupyter Notebooks](https://jupyter.designsafe-ci.org). +`dapi` is a library that simplifies the process of submitting, running, and monitoring [TAPIS v2 / AgavePy](https://agavepy.readthedocs.io/en/latest/index.html) jobs on [DesignSafe](https://designsafe-ci.org) via [Jupyter Notebooks](https://jupyter.designsafe-ci.org). ## Features -* Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. `dsjobs` abstracts away the complexities. +* Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. `dapi` abstracts away the complexities. * Seamless Integration with DesignSafe Jupyter Notebooks: Launch DesignSafe applications directly from the Jupyter environment. ## Installation ```shell -pip3 install dsjobs +pip3 install dapi ``` """ diff --git a/dsjobs/dir.py b/dapi/jobs/dir.py similarity index 100% rename from dsjobs/dir.py rename to dapi/jobs/dir.py diff --git a/dsjobs/jobs.py b/dapi/jobs/jobs.py similarity index 99% rename from dsjobs/jobs.py rename to dapi/jobs/jobs.py index 62f5c7a..bd78835 100644 --- a/dsjobs/jobs.py +++ b/dapi/jobs/jobs.py @@ -8,7 +8,6 @@ # level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" # ) - def get_status(ag, job_id, time_lapse=15): """ Retrieves and monitors the status of a job from Agave. diff --git a/docs/dapi/dir.html b/docs/dapi/dir.html new file mode 100644 index 0000000..efe6956 --- /dev/null +++ b/docs/dapi/dir.html @@ -0,0 +1,193 @@ + + + + + + +dapi.dir API documentation + + + + + + + + + + + +
+
+
+

Module dapi.dir

+
+
+
+ +Expand source code + +
import os
+
+
+def get_ds_path_uri(ag, path):
+    """
+    Given a path on DesignSafe, determine the correct input URI.
+
+    Args:
+        ag (object): Agave object to fetch profiles or metadata.
+        path (str): The directory path.
+
+    Returns:
+        str: The corresponding input URI.
+
+    Raises:
+        ValueError: If no matching directory pattern is found.
+    """
+
+    # If any of the following directory patterns are found in the path,
+    # process them accordingly.
+    directory_patterns = [
+        ("jupyter/MyData", "designsafe.storage.default", True),
+        ("jupyter/mydata", "designsafe.storage.default", True),
+        ("jupyter/CommunityData", "designsafe.storage.community", False),
+        ("/MyData", "designsafe.storage.default", True),
+        ("/mydata", "designsafe.storage.default", True),
+    ]
+
+    for pattern, storage, use_username in directory_patterns:
+        if pattern in path:
+            path = path.split(pattern).pop()
+            input_dir = ag.profiles.get()["username"] + path if use_username else path
+            input_uri = f"agave://{storage}/{input_dir}"
+            return input_uri.replace(" ", "%20")
+
+    project_patterns = [
+        ("jupyter/MyProjects", "project-"),
+        ("jupyter/projects", "project-"),
+    ]
+
+    for pattern, prefix in project_patterns:
+        if pattern in path:
+            path = path.split(pattern + "/").pop()
+            project_id = path.split("/")[0]
+            query = {"value.projectId": str(project_id)}
+            path = path.split(project_id).pop()
+            project_uuid = ag.meta.listMetadata(q=str(query))[0]["uuid"]
+            input_uri = f"agave://{prefix}{project_uuid}{path}"
+            return input_uri.replace(" ", "%20")
+
+    raise ValueError(f"No matching directory pattern found for: {path}")
+
+
+
+
+
+
+
+

Functions

+
+
+def get_ds_path_uri(ag, path) +
+
+

Given a path on DesignSafe, determine the correct input URI.

+

Args

+
+
ag : object
+
Agave object to fetch profiles or metadata.
+
path : str
+
The directory path.
+
+

Returns

+
+
str
+
The corresponding input URI.
+
+

Raises

+
+
ValueError
+
If no matching directory pattern is found.
+
+
+ +Expand source code + +
def get_ds_path_uri(ag, path):
+    """
+    Given a path on DesignSafe, determine the correct input URI.
+
+    Args:
+        ag (object): Agave object to fetch profiles or metadata.
+        path (str): The directory path.
+
+    Returns:
+        str: The corresponding input URI.
+
+    Raises:
+        ValueError: If no matching directory pattern is found.
+    """
+
+    # If any of the following directory patterns are found in the path,
+    # process them accordingly.
+    directory_patterns = [
+        ("jupyter/MyData", "designsafe.storage.default", True),
+        ("jupyter/mydata", "designsafe.storage.default", True),
+        ("jupyter/CommunityData", "designsafe.storage.community", False),
+        ("/MyData", "designsafe.storage.default", True),
+        ("/mydata", "designsafe.storage.default", True),
+    ]
+
+    for pattern, storage, use_username in directory_patterns:
+        if pattern in path:
+            path = path.split(pattern).pop()
+            input_dir = ag.profiles.get()["username"] + path if use_username else path
+            input_uri = f"agave://{storage}/{input_dir}"
+            return input_uri.replace(" ", "%20")
+
+    project_patterns = [
+        ("jupyter/MyProjects", "project-"),
+        ("jupyter/projects", "project-"),
+    ]
+
+    for pattern, prefix in project_patterns:
+        if pattern in path:
+            path = path.split(pattern + "/").pop()
+            project_id = path.split("/")[0]
+            query = {"value.projectId": str(project_id)}
+            path = path.split(project_id).pop()
+            project_uuid = ag.meta.listMetadata(q=str(query))[0]["uuid"]
+            input_uri = f"agave://{prefix}{project_uuid}{path}"
+            return input_uri.replace(" ", "%20")
+
+    raise ValueError(f"No matching directory pattern found for: {path}")
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/dapi/index.html b/docs/dapi/index.html new file mode 100644 index 0000000..e2fc7fd --- /dev/null +++ b/docs/dapi/index.html @@ -0,0 +1,60 @@ + + + + + + +dapi API documentation + + + + + + + + + + + +
+
+
+

Namespace dapi

+
+
+
+
+

Sub-modules

+
+
dapi.jobs
+
+

dapi is a library that simplifies the process of submitting, running, and monitoring [TAPIS v2 / …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/dapi/jobs.html b/docs/dapi/jobs.html new file mode 100644 index 0000000..ca4ff96 --- /dev/null +++ b/docs/dapi/jobs.html @@ -0,0 +1,605 @@ + + + + + + +dapi.jobs API documentation + + + + + + + + + + + +
+
+
+

Module dapi.jobs

+
+
+
+ +Expand source code + +
import time
+from datetime import datetime, timedelta, timezone
+from tqdm import tqdm
+import logging
+
+# Configuring the logging system
+# logging.basicConfig(
+#     level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
+# )
+
+def get_status(ag, job_id, time_lapse=15):
+    """
+    Retrieves and monitors the status of a job from Agave.
+
+    This function initially waits for the job to start, displaying its progress using
+    a tqdm progress bar. Once the job starts, it monitors the job's status up to
+    a maximum duration specified by the job's "maxHours". If the job completes or fails
+    before reaching this maximum duration, it returns the job's final status.
+
+    Args:
+      ag (object): The Agave job object used to interact with the job.
+      job_id (str): The unique identifier of the job to monitor.
+      time_lapse (int, optional): Time interval, in seconds, to wait between status
+        checks. Defaults to 15 seconds.
+
+    Returns:
+      str: The final status of the job. Typical values include "FINISHED", "FAILED",
+           and "STOPPED".
+
+    Raises:
+      No exceptions are explicitly raised, but potential exceptions raised by the Agave
+      job object or other called functions/methods will propagate.
+    """
+
+    previous_status = None
+    # Initially check if the job is already running
+    status = ag.jobs.getStatus(jobId=job_id)["status"]
+
+    job_details = ag.jobs.get(jobId=job_id)
+    max_hours = job_details["maxHours"]
+
+    # Using tqdm to provide visual feedback while waiting for job to start
+    with tqdm(desc="Waiting for job to start", dynamic_ncols=True) as pbar:
+        while status not in ["RUNNING", "FINISHED", "FAILED", "STOPPED"]:
+            time.sleep(time_lapse)
+            status = ag.jobs.getStatus(jobId=job_id)["status"]
+            pbar.update(1)
+            pbar.set_postfix_str(f"Status: {status}")
+
+    # Once the job is running, monitor it for up to maxHours
+    max_iterations = int(max_hours * 3600 // time_lapse)
+
+    # Using tqdm for progress bar
+    for _ in tqdm(range(max_iterations), desc="Monitoring job", ncols=100):
+        status = ag.jobs.getStatus(jobId=job_id)["status"]
+
+        # Print status if it has changed
+        if status != previous_status:
+            tqdm.write(f"\tStatus: {status}")
+            previous_status = status
+
+        # Break the loop if job reaches one of these statuses
+        if status in ["FINISHED", "FAILED", "STOPPED"]:
+            break
+
+        time.sleep(time_lapse)
+    else:
+        # This block will execute if the for loop completes without a 'break'
+        logging.warn("Warning: Maximum monitoring time reached!")
+
+    return status
+
+
+def runtime_summary(ag, job_id, verbose=False):
+    """Get the runtime of a job.
+
+    Args:
+        ag (object): The Agave object that has the job details.
+        job_id (str): The ID of the job for which the runtime needs to be determined.
+        verbose (bool): If True, prints all statuses. Otherwise, prints only specific statuses.
+
+    Returns:
+        None: This function doesn't return a value, but it prints the runtime details.
+
+    """
+
+    print("Runtime Summary")
+    print("---------------")
+
+    job_history = ag.jobs.getHistory(jobId=job_id)
+    total_time = job_history[-1]["created"] - job_history[0]["created"]
+
+    status_times = {}
+
+    for i in range(len(job_history) - 1):
+        current_status = job_history[i]["status"]
+        elapsed_time = job_history[i + 1]["created"] - job_history[i]["created"]
+
+        # Aggregate times for each status
+        if current_status in status_times:
+            status_times[current_status] += elapsed_time
+        else:
+            status_times[current_status] = elapsed_time
+
+    # Filter the statuses if verbose is False
+    if not verbose:
+        filtered_statuses = {
+            "PENDING",
+            "QUEUED",
+            "RUNNING",
+            "FINISHED",
+            "FAILED",
+        }
+        status_times = {
+            status: time
+            for status, time in status_times.items()
+            if status in filtered_statuses
+        }
+
+    # Determine the max width of status names for alignment
+    max_status_width = max(len(status) for status in status_times.keys())
+
+    # Print the aggregated times for each unique status in a table format
+    for status, time in status_times.items():
+        print(f"{status.upper():<{max_status_width + 2}} time: {time}")
+
+    print(f"{'TOTAL':<{max_status_width + 2}} time: {total_time}")
+    print("---------------")
+
+
+def generate_job_info(
+    ag,
+    appid: str,
+    jobname: str = "dsjob",
+    queue: str = "development",
+    nnodes: int = 1,
+    nprocessors: int = 1,
+    runtime: str = "00:10:00",
+    inputs=None,
+    parameters=None,
+) -> dict:
+    """Generate a job information dictionary based on provided arguments.
+
+    Args:
+        ag (object): The Agave object to interact with the platform.
+        appid (str): The application ID for the job.
+        jobname (str, optional): The name of the job. Defaults to 'dsjob'.
+        queue (str, optional): The batch queue name. Defaults to 'skx-dev'.
+        nnodes (int, optional): The number of nodes required. Defaults to 1.
+        nprocessors (int, optional): The number of processors per node. Defaults to 1.
+        runtime (str, optional): The maximum runtime in the format 'HH:MM:SS'. Defaults to '00:10:00'.
+        inputs (dict, optional): The inputs for the job. Defaults to None.
+        parameters (dict, optional): The parameters for the job. Defaults to None.
+
+    Returns:
+        dict: A dictionary containing the job information.
+
+    Raises:
+        ValueError: If the provided appid is not valid.
+    """
+
+    try:
+        app = ag.apps.get(appId=appid)
+    except Exception:
+        raise ValueError(f"Invalid app ID: {appid}")
+
+    job_info = {
+        "appId": appid,
+        "name": jobname,
+        "batchQueue": queue,
+        "nodeCount": nnodes,
+        "processorsPerNode": nprocessors,
+        "memoryPerNode": "1",
+        "maxRunTime": runtime,
+        "archive": True,
+        "inputs": inputs,
+        "parameters": parameters,
+    }
+
+    return job_info
+
+
+def get_archive_path(ag, job_id):
+    """
+    Get the archive path for a given job ID and modifies the user directory
+    to '/home/jupyter/MyData'.
+
+    Args:
+        ag (object): The Agave object to interact with the platform.
+        job_id (str): The job ID to retrieve the archive path for.
+
+    Returns:
+        str: The modified archive path.
+
+    Raises:
+        ValueError: If the archivePath format is unexpected.
+    """
+
+    # Fetch the job info.
+    job_info = ag.jobs.get(jobId=job_id)
+
+    # Try to split the archive path to extract the user.
+    try:
+        user, _ = job_info.archivePath.split("/", 1)
+    except ValueError:
+        raise ValueError(f"Unexpected archivePath format for jobId={job_id}")
+
+    # Construct the new path.
+    new_path = job_info.archivePath.replace(user, "/home/jupyter/MyData")
+
+    return new_path
+
+
+
+
+
+
+
+

Functions

+
+
+def generate_job_info(ag, appid: str, jobname: str = 'dsjob', queue: str = 'development', nnodes: int = 1, nprocessors: int = 1, runtime: str = '00:10:00', inputs=None, parameters=None) ‑> dict +
+
+

Generate a job information dictionary based on provided arguments.

+

Args

+
+
ag : object
+
The Agave object to interact with the platform.
+
appid : str
+
The application ID for the job.
+
jobname : str, optional
+
The name of the job. Defaults to 'dsjob'.
+
queue : str, optional
+
The batch queue name. Defaults to 'skx-dev'.
+
nnodes : int, optional
+
The number of nodes required. Defaults to 1.
+
nprocessors : int, optional
+
The number of processors per node. Defaults to 1.
+
runtime : str, optional
+
The maximum runtime in the format 'HH:MM:SS'. Defaults to '00:10:00'.
+
inputs : dict, optional
+
The inputs for the job. Defaults to None.
+
parameters : dict, optional
+
The parameters for the job. Defaults to None.
+
+

Returns

+
+
dict
+
A dictionary containing the job information.
+
+

Raises

+
+
ValueError
+
If the provided appid is not valid.
+
+
+ +Expand source code + +
def generate_job_info(
+    ag,
+    appid: str,
+    jobname: str = "dsjob",
+    queue: str = "development",
+    nnodes: int = 1,
+    nprocessors: int = 1,
+    runtime: str = "00:10:00",
+    inputs=None,
+    parameters=None,
+) -> dict:
+    """Generate a job information dictionary based on provided arguments.
+
+    Args:
+        ag (object): The Agave object to interact with the platform.
+        appid (str): The application ID for the job.
+        jobname (str, optional): The name of the job. Defaults to 'dsjob'.
+        queue (str, optional): The batch queue name. Defaults to 'skx-dev'.
+        nnodes (int, optional): The number of nodes required. Defaults to 1.
+        nprocessors (int, optional): The number of processors per node. Defaults to 1.
+        runtime (str, optional): The maximum runtime in the format 'HH:MM:SS'. Defaults to '00:10:00'.
+        inputs (dict, optional): The inputs for the job. Defaults to None.
+        parameters (dict, optional): The parameters for the job. Defaults to None.
+
+    Returns:
+        dict: A dictionary containing the job information.
+
+    Raises:
+        ValueError: If the provided appid is not valid.
+    """
+
+    try:
+        app = ag.apps.get(appId=appid)
+    except Exception:
+        raise ValueError(f"Invalid app ID: {appid}")
+
+    job_info = {
+        "appId": appid,
+        "name": jobname,
+        "batchQueue": queue,
+        "nodeCount": nnodes,
+        "processorsPerNode": nprocessors,
+        "memoryPerNode": "1",
+        "maxRunTime": runtime,
+        "archive": True,
+        "inputs": inputs,
+        "parameters": parameters,
+    }
+
+    return job_info
+
+
+
+def get_archive_path(ag, job_id) +
+
+

Get the archive path for a given job ID and modifies the user directory +to '/home/jupyter/MyData'.

+

Args

+
+
ag : object
+
The Agave object to interact with the platform.
+
job_id : str
+
The job ID to retrieve the archive path for.
+
+

Returns

+
+
str
+
The modified archive path.
+
+

Raises

+
+
ValueError
+
If the archivePath format is unexpected.
+
+
+ +Expand source code + +
def get_archive_path(ag, job_id):
+    """
+    Get the archive path for a given job ID and modifies the user directory
+    to '/home/jupyter/MyData'.
+
+    Args:
+        ag (object): The Agave object to interact with the platform.
+        job_id (str): The job ID to retrieve the archive path for.
+
+    Returns:
+        str: The modified archive path.
+
+    Raises:
+        ValueError: If the archivePath format is unexpected.
+    """
+
+    # Fetch the job info.
+    job_info = ag.jobs.get(jobId=job_id)
+
+    # Try to split the archive path to extract the user.
+    try:
+        user, _ = job_info.archivePath.split("/", 1)
+    except ValueError:
+        raise ValueError(f"Unexpected archivePath format for jobId={job_id}")
+
+    # Construct the new path.
+    new_path = job_info.archivePath.replace(user, "/home/jupyter/MyData")
+
+    return new_path
+
+
+
+def get_status(ag, job_id, time_lapse=15) +
+
+

Retrieves and monitors the status of a job from Agave.

+

This function initially waits for the job to start, displaying its progress using +a tqdm progress bar. Once the job starts, it monitors the job's status up to +a maximum duration specified by the job's "maxHours". If the job completes or fails +before reaching this maximum duration, it returns the job's final status.

+

Args

+
+
ag : object
+
The Agave job object used to interact with the job.
+
job_id : str
+
The unique identifier of the job to monitor.
+
time_lapse : int, optional
+
Time interval, in seconds, to wait between status +checks. Defaults to 15 seconds.
+
+

Returns

+
+
str
+
The final status of the job. Typical values include "FINISHED", "FAILED", +and "STOPPED".
+
+

Raises

+

No exceptions are explicitly raised, but potential exceptions raised by the Agave +job object or other called functions/methods will propagate.

+
+ +Expand source code + +
def get_status(ag, job_id, time_lapse=15):
+    """
+    Retrieves and monitors the status of a job from Agave.
+
+    This function initially waits for the job to start, displaying its progress using
+    a tqdm progress bar. Once the job starts, it monitors the job's status up to
+    a maximum duration specified by the job's "maxHours". If the job completes or fails
+    before reaching this maximum duration, it returns the job's final status.
+
+    Args:
+      ag (object): The Agave job object used to interact with the job.
+      job_id (str): The unique identifier of the job to monitor.
+      time_lapse (int, optional): Time interval, in seconds, to wait between status
+        checks. Defaults to 15 seconds.
+
+    Returns:
+      str: The final status of the job. Typical values include "FINISHED", "FAILED",
+           and "STOPPED".
+
+    Raises:
+      No exceptions are explicitly raised, but potential exceptions raised by the Agave
+      job object or other called functions/methods will propagate.
+    """
+
+    previous_status = None
+    # Initially check if the job is already running
+    status = ag.jobs.getStatus(jobId=job_id)["status"]
+
+    job_details = ag.jobs.get(jobId=job_id)
+    max_hours = job_details["maxHours"]
+
+    # Using tqdm to provide visual feedback while waiting for job to start
+    with tqdm(desc="Waiting for job to start", dynamic_ncols=True) as pbar:
+        while status not in ["RUNNING", "FINISHED", "FAILED", "STOPPED"]:
+            time.sleep(time_lapse)
+            status = ag.jobs.getStatus(jobId=job_id)["status"]
+            pbar.update(1)
+            pbar.set_postfix_str(f"Status: {status}")
+
+    # Once the job is running, monitor it for up to maxHours
+    max_iterations = int(max_hours * 3600 // time_lapse)
+
+    # Using tqdm for progress bar
+    for _ in tqdm(range(max_iterations), desc="Monitoring job", ncols=100):
+        status = ag.jobs.getStatus(jobId=job_id)["status"]
+
+        # Print status if it has changed
+        if status != previous_status:
+            tqdm.write(f"\tStatus: {status}")
+            previous_status = status
+
+        # Break the loop if job reaches one of these statuses
+        if status in ["FINISHED", "FAILED", "STOPPED"]:
+            break
+
+        time.sleep(time_lapse)
+    else:
+        # This block will execute if the for loop completes without a 'break'
+        logging.warn("Warning: Maximum monitoring time reached!")
+
+    return status
+
+
+
+def runtime_summary(ag, job_id, verbose=False) +
+
+

Get the runtime of a job.

+

Args

+
+
ag : object
+
The Agave object that has the job details.
+
job_id : str
+
The ID of the job for which the runtime needs to be determined.
+
verbose : bool
+
If True, prints all statuses. Otherwise, prints only specific statuses.
+
+

Returns

+
+
None
+
This function doesn't return a value, but it prints the runtime details.
+
+
+ +Expand source code + +
def runtime_summary(ag, job_id, verbose=False):
+    """Get the runtime of a job.
+
+    Args:
+        ag (object): The Agave object that has the job details.
+        job_id (str): The ID of the job for which the runtime needs to be determined.
+        verbose (bool): If True, prints all statuses. Otherwise, prints only specific statuses.
+
+    Returns:
+        None: This function doesn't return a value, but it prints the runtime details.
+
+    """
+
+    print("Runtime Summary")
+    print("---------------")
+
+    job_history = ag.jobs.getHistory(jobId=job_id)
+    total_time = job_history[-1]["created"] - job_history[0]["created"]
+
+    status_times = {}
+
+    for i in range(len(job_history) - 1):
+        current_status = job_history[i]["status"]
+        elapsed_time = job_history[i + 1]["created"] - job_history[i]["created"]
+
+        # Aggregate times for each status
+        if current_status in status_times:
+            status_times[current_status] += elapsed_time
+        else:
+            status_times[current_status] = elapsed_time
+
+    # Filter the statuses if verbose is False
+    if not verbose:
+        filtered_statuses = {
+            "PENDING",
+            "QUEUED",
+            "RUNNING",
+            "FINISHED",
+            "FAILED",
+        }
+        status_times = {
+            status: time
+            for status, time in status_times.items()
+            if status in filtered_statuses
+        }
+
+    # Determine the max width of status names for alignment
+    max_status_width = max(len(status) for status in status_times.keys())
+
+    # Print the aggregated times for each unique status in a table format
+    for status, time in status_times.items():
+        print(f"{status.upper():<{max_status_width + 2}} time: {time}")
+
+    print(f"{'TOTAL':<{max_status_width + 2}} time: {total_time}")
+    print("---------------")
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/dapi/jobs/dir.html b/docs/dapi/jobs/dir.html new file mode 100644 index 0000000..514cb97 --- /dev/null +++ b/docs/dapi/jobs/dir.html @@ -0,0 +1,193 @@ + + + + + + +dapi.jobs.dir API documentation + + + + + + + + + + + +
+
+
+

Module dapi.jobs.dir

+
+
+
+ +Expand source code + +
import os
+
+
+def get_ds_path_uri(ag, path):
+    """
+    Given a path on DesignSafe, determine the correct input URI.
+
+    Args:
+        ag (object): Agave object to fetch profiles or metadata.
+        path (str): The directory path.
+
+    Returns:
+        str: The corresponding input URI.
+
+    Raises:
+        ValueError: If no matching directory pattern is found.
+    """
+
+    # If any of the following directory patterns are found in the path,
+    # process them accordingly.
+    directory_patterns = [
+        ("jupyter/MyData", "designsafe.storage.default", True),
+        ("jupyter/mydata", "designsafe.storage.default", True),
+        ("jupyter/CommunityData", "designsafe.storage.community", False),
+        ("/MyData", "designsafe.storage.default", True),
+        ("/mydata", "designsafe.storage.default", True),
+    ]
+
+    for pattern, storage, use_username in directory_patterns:
+        if pattern in path:
+            path = path.split(pattern).pop()
+            input_dir = ag.profiles.get()["username"] + path if use_username else path
+            input_uri = f"agave://{storage}/{input_dir}"
+            return input_uri.replace(" ", "%20")
+
+    project_patterns = [
+        ("jupyter/MyProjects", "project-"),
+        ("jupyter/projects", "project-"),
+    ]
+
+    for pattern, prefix in project_patterns:
+        if pattern in path:
+            path = path.split(pattern + "/").pop()
+            project_id = path.split("/")[0]
+            query = {"value.projectId": str(project_id)}
+            path = path.split(project_id).pop()
+            project_uuid = ag.meta.listMetadata(q=str(query))[0]["uuid"]
+            input_uri = f"agave://{prefix}{project_uuid}{path}"
+            return input_uri.replace(" ", "%20")
+
+    raise ValueError(f"No matching directory pattern found for: {path}")
+
+
+
+
+
+
+
+

Functions

+
+
+def get_ds_path_uri(ag, path) +
+
+

Given a path on DesignSafe, determine the correct input URI.

+

Args

+
+
ag : object
+
Agave object to fetch profiles or metadata.
+
path : str
+
The directory path.
+
+

Returns

+
+
str
+
The corresponding input URI.
+
+

Raises

+
+
ValueError
+
If no matching directory pattern is found.
+
+
+ +Expand source code + +
def get_ds_path_uri(ag, path):
+    """
+    Given a path on DesignSafe, determine the correct input URI.
+
+    Args:
+        ag (object): Agave object to fetch profiles or metadata.
+        path (str): The directory path.
+
+    Returns:
+        str: The corresponding input URI.
+
+    Raises:
+        ValueError: If no matching directory pattern is found.
+    """
+
+    # If any of the following directory patterns are found in the path,
+    # process them accordingly.
+    directory_patterns = [
+        ("jupyter/MyData", "designsafe.storage.default", True),
+        ("jupyter/mydata", "designsafe.storage.default", True),
+        ("jupyter/CommunityData", "designsafe.storage.community", False),
+        ("/MyData", "designsafe.storage.default", True),
+        ("/mydata", "designsafe.storage.default", True),
+    ]
+
+    for pattern, storage, use_username in directory_patterns:
+        if pattern in path:
+            path = path.split(pattern).pop()
+            input_dir = ag.profiles.get()["username"] + path if use_username else path
+            input_uri = f"agave://{storage}/{input_dir}"
+            return input_uri.replace(" ", "%20")
+
+    project_patterns = [
+        ("jupyter/MyProjects", "project-"),
+        ("jupyter/projects", "project-"),
+    ]
+
+    for pattern, prefix in project_patterns:
+        if pattern in path:
+            path = path.split(pattern + "/").pop()
+            project_id = path.split("/")[0]
+            query = {"value.projectId": str(project_id)}
+            path = path.split(project_id).pop()
+            project_uuid = ag.meta.listMetadata(q=str(query))[0]["uuid"]
+            input_uri = f"agave://{prefix}{project_uuid}{path}"
+            return input_uri.replace(" ", "%20")
+
+    raise ValueError(f"No matching directory pattern found for: {path}")
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/dsjobs/index.html b/docs/dapi/jobs/index.html similarity index 81% rename from docs/dsjobs/index.html rename to docs/dapi/jobs/index.html index 3685c53..e2e246c 100644 --- a/docs/dsjobs/index.html +++ b/docs/dapi/jobs/index.html @@ -4,8 +4,8 @@ -dsjobs API documentation - +dapi.jobs API documentation + @@ -19,40 +19,40 @@
-

Package dsjobs

+

Module dapi.jobs

-

dsjobs is a library that simplifies the process of submitting, running, and monitoring TAPIS v2 / AgavePy jobs on DesignSafe via Jupyter Notebooks.

+

dapi is a library that simplifies the process of submitting, running, and monitoring TAPIS v2 / AgavePy jobs on DesignSafe via Jupyter Notebooks.

Features

  • -

    Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. dsjobs abstracts away the complexities.

    +

    Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. dapi abstracts away the complexities.

  • Seamless Integration with DesignSafe Jupyter Notebooks: Launch DesignSafe applications directly from the Jupyter environment.

Installation

-
pip3 install dsjobs
+
pip3 install dapi
 
Expand source code
"""
-`dsjobs` is a library that simplifies the process of submitting, running, and monitoring [TAPIS v2 / AgavePy](https://agavepy.readthedocs.io/en/latest/index.html) jobs on [DesignSafe](https://designsafe-ci.org) via [Jupyter Notebooks](https://jupyter.designsafe-ci.org).
+`dapi` is a library that simplifies the process of submitting, running, and monitoring [TAPIS v2 / AgavePy](https://agavepy.readthedocs.io/en/latest/index.html) jobs on [DesignSafe](https://designsafe-ci.org) via [Jupyter Notebooks](https://jupyter.designsafe-ci.org).
 
 
 ## Features
 
-* Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. `dsjobs` abstracts away the complexities.
+* Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. `dapi` abstracts away the complexities.
 
 * Seamless Integration with DesignSafe Jupyter Notebooks: Launch DesignSafe applications directly from the Jupyter environment.
 
 ## Installation
 
 ```shell
-pip3 install dsjobs
+pip3 install dapi
 ```
 
 """
@@ -63,11 +63,11 @@ 

Installation

Sub-modules

-
dsjobs.dir
+
dapi.jobs.dir
-
dsjobs.jobs
+
dapi.jobs.jobs
@@ -89,10 +89,15 @@

Index

diff --git a/docs/dapi/jobs/jobs.html b/docs/dapi/jobs/jobs.html new file mode 100644 index 0000000..5bade0e --- /dev/null +++ b/docs/dapi/jobs/jobs.html @@ -0,0 +1,605 @@ + + + + + + +dapi.jobs.jobs API documentation + + + + + + + + + + + +
+
+
+

Module dapi.jobs.jobs

+
+
+
+ +Expand source code + +
import time
+from datetime import datetime, timedelta, timezone
+from tqdm import tqdm
+import logging
+
+# Configuring the logging system
+# logging.basicConfig(
+#     level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
+# )
+
+def get_status(ag, job_id, time_lapse=15):
+    """
+    Retrieves and monitors the status of a job from Agave.
+
+    This function initially waits for the job to start, displaying its progress using
+    a tqdm progress bar. Once the job starts, it monitors the job's status up to
+    a maximum duration specified by the job's "maxHours". If the job completes or fails
+    before reaching this maximum duration, it returns the job's final status.
+
+    Args:
+      ag (object): The Agave job object used to interact with the job.
+      job_id (str): The unique identifier of the job to monitor.
+      time_lapse (int, optional): Time interval, in seconds, to wait between status
+        checks. Defaults to 15 seconds.
+
+    Returns:
+      str: The final status of the job. Typical values include "FINISHED", "FAILED",
+           and "STOPPED".
+
+    Raises:
+      No exceptions are explicitly raised, but potential exceptions raised by the Agave
+      job object or other called functions/methods will propagate.
+    """
+
+    previous_status = None
+    # Initially check if the job is already running
+    status = ag.jobs.getStatus(jobId=job_id)["status"]
+
+    job_details = ag.jobs.get(jobId=job_id)
+    max_hours = job_details["maxHours"]
+
+    # Using tqdm to provide visual feedback while waiting for job to start
+    with tqdm(desc="Waiting for job to start", dynamic_ncols=True) as pbar:
+        while status not in ["RUNNING", "FINISHED", "FAILED", "STOPPED"]:
+            time.sleep(time_lapse)
+            status = ag.jobs.getStatus(jobId=job_id)["status"]
+            pbar.update(1)
+            pbar.set_postfix_str(f"Status: {status}")
+
+    # Once the job is running, monitor it for up to maxHours
+    max_iterations = int(max_hours * 3600 // time_lapse)
+
+    # Using tqdm for progress bar
+    for _ in tqdm(range(max_iterations), desc="Monitoring job", ncols=100):
+        status = ag.jobs.getStatus(jobId=job_id)["status"]
+
+        # Print status if it has changed
+        if status != previous_status:
+            tqdm.write(f"\tStatus: {status}")
+            previous_status = status
+
+        # Break the loop if job reaches one of these statuses
+        if status in ["FINISHED", "FAILED", "STOPPED"]:
+            break
+
+        time.sleep(time_lapse)
+    else:
+        # This block will execute if the for loop completes without a 'break'
+        logging.warn("Warning: Maximum monitoring time reached!")
+
+    return status
+
+
+def runtime_summary(ag, job_id, verbose=False):
+    """Get the runtime of a job.
+
+    Args:
+        ag (object): The Agave object that has the job details.
+        job_id (str): The ID of the job for which the runtime needs to be determined.
+        verbose (bool): If True, prints all statuses. Otherwise, prints only specific statuses.
+
+    Returns:
+        None: This function doesn't return a value, but it prints the runtime details.
+
+    """
+
+    print("Runtime Summary")
+    print("---------------")
+
+    job_history = ag.jobs.getHistory(jobId=job_id)
+    total_time = job_history[-1]["created"] - job_history[0]["created"]
+
+    status_times = {}
+
+    for i in range(len(job_history) - 1):
+        current_status = job_history[i]["status"]
+        elapsed_time = job_history[i + 1]["created"] - job_history[i]["created"]
+
+        # Aggregate times for each status
+        if current_status in status_times:
+            status_times[current_status] += elapsed_time
+        else:
+            status_times[current_status] = elapsed_time
+
+    # Filter the statuses if verbose is False
+    if not verbose:
+        filtered_statuses = {
+            "PENDING",
+            "QUEUED",
+            "RUNNING",
+            "FINISHED",
+            "FAILED",
+        }
+        status_times = {
+            status: time
+            for status, time in status_times.items()
+            if status in filtered_statuses
+        }
+
+    # Determine the max width of status names for alignment
+    max_status_width = max(len(status) for status in status_times.keys())
+
+    # Print the aggregated times for each unique status in a table format
+    for status, time in status_times.items():
+        print(f"{status.upper():<{max_status_width + 2}} time: {time}")
+
+    print(f"{'TOTAL':<{max_status_width + 2}} time: {total_time}")
+    print("---------------")
+
+
+def generate_job_info(
+    ag,
+    appid: str,
+    jobname: str = "dsjob",
+    queue: str = "development",
+    nnodes: int = 1,
+    nprocessors: int = 1,
+    runtime: str = "00:10:00",
+    inputs=None,
+    parameters=None,
+) -> dict:
+    """Generate a job information dictionary based on provided arguments.
+
+    Args:
+        ag (object): The Agave object to interact with the platform.
+        appid (str): The application ID for the job.
+        jobname (str, optional): The name of the job. Defaults to 'dsjob'.
+        queue (str, optional): The batch queue name. Defaults to 'skx-dev'.
+        nnodes (int, optional): The number of nodes required. Defaults to 1.
+        nprocessors (int, optional): The number of processors per node. Defaults to 1.
+        runtime (str, optional): The maximum runtime in the format 'HH:MM:SS'. Defaults to '00:10:00'.
+        inputs (dict, optional): The inputs for the job. Defaults to None.
+        parameters (dict, optional): The parameters for the job. Defaults to None.
+
+    Returns:
+        dict: A dictionary containing the job information.
+
+    Raises:
+        ValueError: If the provided appid is not valid.
+    """
+
+    try:
+        app = ag.apps.get(appId=appid)
+    except Exception:
+        raise ValueError(f"Invalid app ID: {appid}")
+
+    job_info = {
+        "appId": appid,
+        "name": jobname,
+        "batchQueue": queue,
+        "nodeCount": nnodes,
+        "processorsPerNode": nprocessors,
+        "memoryPerNode": "1",
+        "maxRunTime": runtime,
+        "archive": True,
+        "inputs": inputs,
+        "parameters": parameters,
+    }
+
+    return job_info
+
+
+def get_archive_path(ag, job_id):
+    """
+    Get the archive path for a given job ID and modifies the user directory
+    to '/home/jupyter/MyData'.
+
+    Args:
+        ag (object): The Agave object to interact with the platform.
+        job_id (str): The job ID to retrieve the archive path for.
+
+    Returns:
+        str: The modified archive path.
+
+    Raises:
+        ValueError: If the archivePath format is unexpected.
+    """
+
+    # Fetch the job info.
+    job_info = ag.jobs.get(jobId=job_id)
+
+    # Try to split the archive path to extract the user.
+    try:
+        user, _ = job_info.archivePath.split("/", 1)
+    except ValueError:
+        raise ValueError(f"Unexpected archivePath format for jobId={job_id}")
+
+    # Construct the new path.
+    new_path = job_info.archivePath.replace(user, "/home/jupyter/MyData")
+
+    return new_path
+
+
+
+
+
+
+
+

Functions

+
+
+def generate_job_info(ag, appid: str, jobname: str = 'dsjob', queue: str = 'development', nnodes: int = 1, nprocessors: int = 1, runtime: str = '00:10:00', inputs=None, parameters=None) ‑> dict +
+
+

Generate a job information dictionary based on provided arguments.

+

Args

+
+
ag : object
+
The Agave object to interact with the platform.
+
appid : str
+
The application ID for the job.
+
jobname : str, optional
+
The name of the job. Defaults to 'dsjob'.
+
queue : str, optional
+
The batch queue name. Defaults to 'skx-dev'.
+
nnodes : int, optional
+
The number of nodes required. Defaults to 1.
+
nprocessors : int, optional
+
The number of processors per node. Defaults to 1.
+
runtime : str, optional
+
The maximum runtime in the format 'HH:MM:SS'. Defaults to '00:10:00'.
+
inputs : dict, optional
+
The inputs for the job. Defaults to None.
+
parameters : dict, optional
+
The parameters for the job. Defaults to None.
+
+

Returns

+
+
dict
+
A dictionary containing the job information.
+
+

Raises

+
+
ValueError
+
If the provided appid is not valid.
+
+
+ +Expand source code + +
def generate_job_info(
+    ag,
+    appid: str,
+    jobname: str = "dsjob",
+    queue: str = "development",
+    nnodes: int = 1,
+    nprocessors: int = 1,
+    runtime: str = "00:10:00",
+    inputs=None,
+    parameters=None,
+) -> dict:
+    """Generate a job information dictionary based on provided arguments.
+
+    Args:
+        ag (object): The Agave object to interact with the platform.
+        appid (str): The application ID for the job.
+        jobname (str, optional): The name of the job. Defaults to 'dsjob'.
+        queue (str, optional): The batch queue name. Defaults to 'skx-dev'.
+        nnodes (int, optional): The number of nodes required. Defaults to 1.
+        nprocessors (int, optional): The number of processors per node. Defaults to 1.
+        runtime (str, optional): The maximum runtime in the format 'HH:MM:SS'. Defaults to '00:10:00'.
+        inputs (dict, optional): The inputs for the job. Defaults to None.
+        parameters (dict, optional): The parameters for the job. Defaults to None.
+
+    Returns:
+        dict: A dictionary containing the job information.
+
+    Raises:
+        ValueError: If the provided appid is not valid.
+    """
+
+    try:
+        app = ag.apps.get(appId=appid)
+    except Exception:
+        raise ValueError(f"Invalid app ID: {appid}")
+
+    job_info = {
+        "appId": appid,
+        "name": jobname,
+        "batchQueue": queue,
+        "nodeCount": nnodes,
+        "processorsPerNode": nprocessors,
+        "memoryPerNode": "1",
+        "maxRunTime": runtime,
+        "archive": True,
+        "inputs": inputs,
+        "parameters": parameters,
+    }
+
+    return job_info
+
+
+
+def get_archive_path(ag, job_id) +
+
+

Get the archive path for a given job ID and modifies the user directory +to '/home/jupyter/MyData'.

+

Args

+
+
ag : object
+
The Agave object to interact with the platform.
+
job_id : str
+
The job ID to retrieve the archive path for.
+
+

Returns

+
+
str
+
The modified archive path.
+
+

Raises

+
+
ValueError
+
If the archivePath format is unexpected.
+
+
+ +Expand source code + +
def get_archive_path(ag, job_id):
+    """
+    Get the archive path for a given job ID and modifies the user directory
+    to '/home/jupyter/MyData'.
+
+    Args:
+        ag (object): The Agave object to interact with the platform.
+        job_id (str): The job ID to retrieve the archive path for.
+
+    Returns:
+        str: The modified archive path.
+
+    Raises:
+        ValueError: If the archivePath format is unexpected.
+    """
+
+    # Fetch the job info.
+    job_info = ag.jobs.get(jobId=job_id)
+
+    # Try to split the archive path to extract the user.
+    try:
+        user, _ = job_info.archivePath.split("/", 1)
+    except ValueError:
+        raise ValueError(f"Unexpected archivePath format for jobId={job_id}")
+
+    # Construct the new path.
+    new_path = job_info.archivePath.replace(user, "/home/jupyter/MyData")
+
+    return new_path
+
+
+
+def get_status(ag, job_id, time_lapse=15) +
+
+

Retrieves and monitors the status of a job from Agave.

+

This function initially waits for the job to start, displaying its progress using +a tqdm progress bar. Once the job starts, it monitors the job's status up to +a maximum duration specified by the job's "maxHours". If the job completes or fails +before reaching this maximum duration, it returns the job's final status.

+

Args

+
+
ag : object
+
The Agave job object used to interact with the job.
+
job_id : str
+
The unique identifier of the job to monitor.
+
time_lapse : int, optional
+
Time interval, in seconds, to wait between status +checks. Defaults to 15 seconds.
+
+

Returns

+
+
str
+
The final status of the job. Typical values include "FINISHED", "FAILED", +and "STOPPED".
+
+

Raises

+

No exceptions are explicitly raised, but potential exceptions raised by the Agave +job object or other called functions/methods will propagate.

+
+ +Expand source code + +
def get_status(ag, job_id, time_lapse=15):
+    """
+    Retrieves and monitors the status of a job from Agave.
+
+    This function initially waits for the job to start, displaying its progress using
+    a tqdm progress bar. Once the job starts, it monitors the job's status up to
+    a maximum duration specified by the job's "maxHours". If the job completes or fails
+    before reaching this maximum duration, it returns the job's final status.
+
+    Args:
+      ag (object): The Agave job object used to interact with the job.
+      job_id (str): The unique identifier of the job to monitor.
+      time_lapse (int, optional): Time interval, in seconds, to wait between status
+        checks. Defaults to 15 seconds.
+
+    Returns:
+      str: The final status of the job. Typical values include "FINISHED", "FAILED",
+           and "STOPPED".
+
+    Raises:
+      No exceptions are explicitly raised, but potential exceptions raised by the Agave
+      job object or other called functions/methods will propagate.
+    """
+
+    previous_status = None
+    # Initially check if the job is already running
+    status = ag.jobs.getStatus(jobId=job_id)["status"]
+
+    job_details = ag.jobs.get(jobId=job_id)
+    max_hours = job_details["maxHours"]
+
+    # Using tqdm to provide visual feedback while waiting for job to start
+    with tqdm(desc="Waiting for job to start", dynamic_ncols=True) as pbar:
+        while status not in ["RUNNING", "FINISHED", "FAILED", "STOPPED"]:
+            time.sleep(time_lapse)
+            status = ag.jobs.getStatus(jobId=job_id)["status"]
+            pbar.update(1)
+            pbar.set_postfix_str(f"Status: {status}")
+
+    # Once the job is running, monitor it for up to maxHours
+    max_iterations = int(max_hours * 3600 // time_lapse)
+
+    # Using tqdm for progress bar
+    for _ in tqdm(range(max_iterations), desc="Monitoring job", ncols=100):
+        status = ag.jobs.getStatus(jobId=job_id)["status"]
+
+        # Print status if it has changed
+        if status != previous_status:
+            tqdm.write(f"\tStatus: {status}")
+            previous_status = status
+
+        # Break the loop if job reaches one of these statuses
+        if status in ["FINISHED", "FAILED", "STOPPED"]:
+            break
+
+        time.sleep(time_lapse)
+    else:
+        # This block will execute if the for loop completes without a 'break'
+        logging.warn("Warning: Maximum monitoring time reached!")
+
+    return status
+
+
+
+def runtime_summary(ag, job_id, verbose=False) +
+
+

Get the runtime of a job.

+

Args

+
+
ag : object
+
The Agave object that has the job details.
+
job_id : str
+
The ID of the job for which the runtime needs to be determined.
+
verbose : bool
+
If True, prints all statuses. Otherwise, prints only specific statuses.
+
+

Returns

+
+
None
+
This function doesn't return a value, but it prints the runtime details.
+
+
+ +Expand source code + +
def runtime_summary(ag, job_id, verbose=False):
+    """Get the runtime of a job.
+
+    Args:
+        ag (object): The Agave object that has the job details.
+        job_id (str): The ID of the job for which the runtime needs to be determined.
+        verbose (bool): If True, prints all statuses. Otherwise, prints only specific statuses.
+
+    Returns:
+        None: This function doesn't return a value, but it prints the runtime details.
+
+    """
+
+    print("Runtime Summary")
+    print("---------------")
+
+    job_history = ag.jobs.getHistory(jobId=job_id)
+    total_time = job_history[-1]["created"] - job_history[0]["created"]
+
+    status_times = {}
+
+    for i in range(len(job_history) - 1):
+        current_status = job_history[i]["status"]
+        elapsed_time = job_history[i + 1]["created"] - job_history[i]["created"]
+
+        # Aggregate times for each status
+        if current_status in status_times:
+            status_times[current_status] += elapsed_time
+        else:
+            status_times[current_status] = elapsed_time
+
+    # Filter the statuses if verbose is False
+    if not verbose:
+        filtered_statuses = {
+            "PENDING",
+            "QUEUED",
+            "RUNNING",
+            "FINISHED",
+            "FAILED",
+        }
+        status_times = {
+            status: time
+            for status, time in status_times.items()
+            if status in filtered_statuses
+        }
+
+    # Determine the max width of status names for alignment
+    max_status_width = max(len(status) for status in status_times.keys())
+
+    # Print the aggregated times for each unique status in a table format
+    for status, time in status_times.items():
+        print(f"{status.upper():<{max_status_width + 2}} time: {time}")
+
+    print(f"{'TOTAL':<{max_status_width + 2}} time: {total_time}")
+    print("---------------")
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/dsjobs.html b/docs/dsjobs.html deleted file mode 100644 index 6a2550d..0000000 --- a/docs/dsjobs.html +++ /dev/null @@ -1,278 +0,0 @@ - - - - - - - dsjobs API documentation - - - - - - - - - -
-
-

-dsjobs

- -

dsjobs is a library that simplifies the process of submitting, running, and monitoring TAPIS v2 / AgavePy jobs on DesignSafe via Jupyter Notebooks.

- -

Features

- -
    -
  • Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. dsjobs abstracts away the complexities.

  • -
  • Seamless Integration with DesignSafe Jupyter Notebooks: Launch DesignSafe applications directly from the Jupyter environment.

  • -
- -

Installation

- -
-
pip3 install dsjobs
-
-
-
- - - - - -
 1"""
- 2`dsjobs` is a library that simplifies the process of submitting, running, and monitoring [TAPIS v2 / AgavePy](https://agavepy.readthedocs.io/en/latest/index.html) jobs on [DesignSafe](https://designsafe-ci.org) via [Jupyter Notebooks](https://jupyter.designsafe-ci.org).
- 3
- 4
- 5## Features
- 6
- 7* Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. `dsjobs` abstracts away the complexities.
- 8
- 9* Seamless Integration with DesignSafe Jupyter Notebooks: Launch DesignSafe applications directly from the Jupyter environment.
-10
-11## Installation
-12
-13```shell
-14pip3 install dsjobs
-15```
-16
-17"""
-18from .dir import get_ds_path_uri
-19from .jobs import get_status, runtime_summary, generate_job_info, get_archive_path
-
- - -
-
- - \ No newline at end of file diff --git a/docs/dsjobs/dir.html b/docs/dsjobs/dir.html deleted file mode 100644 index 526945d..0000000 --- a/docs/dsjobs/dir.html +++ /dev/null @@ -1,372 +0,0 @@ - - - - - - - dsjobs.dir API documentation - - - - - - - - - -
-
-

-dsjobs.dir

- - - - - - -
 1import os
- 2
- 3
- 4def get_ds_path_uri(ag, path):
- 5    """
- 6    Given a path on DesignSafe, determine the correct input URI.
- 7
- 8    Args:
- 9        ag (object): Agave object to fetch profiles or metadata.
-10        path (str): The directory path.
-11
-12    Returns:
-13        str: The corresponding input URI.
-14
-15    Raises:
-16        ValueError: If no matching directory pattern is found.
-17    """
-18
-19    # If any of the following directory patterns are found in the path,
-20    # process them accordingly.
-21    directory_patterns = [
-22        ("jupyter/MyData", "designsafe.storage.default", True),
-23        ("jupyter/mydata", "designsafe.storage.default", True),
-24        ("jupyter/CommunityData", "designsafe.storage.community", False),
-25        ("/MyData", "designsafe.storage.default", True),
-26        ("/mydata", "designsafe.storage.default", True),
-27    ]
-28
-29    for pattern, storage, use_username in directory_patterns:
-30        if pattern in path:
-31            path = path.split(pattern).pop()
-32            input_dir = ag.profiles.get()["username"] + path if use_username else path
-33            input_uri = f"agave://{storage}/{input_dir}"
-34            return input_uri.replace(" ", "%20")
-35
-36    project_patterns = [
-37        ("jupyter/MyProjects", "project-"),
-38        ("jupyter/projects", "project-"),
-39    ]
-40
-41    for pattern, prefix in project_patterns:
-42        if pattern in path:
-43            path = path.split(pattern + "/").pop()
-44            project_id = path.split("/")[0]
-45            query = {"value.projectId": str(project_id)}
-46            path = path.split(project_id).pop()
-47            project_uuid = ag.meta.listMetadata(q=str(query))[0]["uuid"]
-48            input_uri = f"agave://{prefix}{project_uuid}{path}"
-49            return input_uri.replace(" ", "%20")
-50
-51    raise ValueError(f"No matching directory pattern found for: {path}")
-
- - -
-
- -
- - def - get_ds_path_uri(ag, path): - - - -
- -
 5def get_ds_path_uri(ag, path):
- 6    """
- 7    Given a path on DesignSafe, determine the correct input URI.
- 8
- 9    Args:
-10        ag (object): Agave object to fetch profiles or metadata.
-11        path (str): The directory path.
-12
-13    Returns:
-14        str: The corresponding input URI.
-15
-16    Raises:
-17        ValueError: If no matching directory pattern is found.
-18    """
-19
-20    # If any of the following directory patterns are found in the path,
-21    # process them accordingly.
-22    directory_patterns = [
-23        ("jupyter/MyData", "designsafe.storage.default", True),
-24        ("jupyter/mydata", "designsafe.storage.default", True),
-25        ("jupyter/CommunityData", "designsafe.storage.community", False),
-26        ("/MyData", "designsafe.storage.default", True),
-27        ("/mydata", "designsafe.storage.default", True),
-28    ]
-29
-30    for pattern, storage, use_username in directory_patterns:
-31        if pattern in path:
-32            path = path.split(pattern).pop()
-33            input_dir = ag.profiles.get()["username"] + path if use_username else path
-34            input_uri = f"agave://{storage}/{input_dir}"
-35            return input_uri.replace(" ", "%20")
-36
-37    project_patterns = [
-38        ("jupyter/MyProjects", "project-"),
-39        ("jupyter/projects", "project-"),
-40    ]
-41
-42    for pattern, prefix in project_patterns:
-43        if pattern in path:
-44            path = path.split(pattern + "/").pop()
-45            project_id = path.split("/")[0]
-46            query = {"value.projectId": str(project_id)}
-47            path = path.split(project_id).pop()
-48            project_uuid = ag.meta.listMetadata(q=str(query))[0]["uuid"]
-49            input_uri = f"agave://{prefix}{project_uuid}{path}"
-50            return input_uri.replace(" ", "%20")
-51
-52    raise ValueError(f"No matching directory pattern found for: {path}")
-
- - -

Given a path on DesignSafe, determine the correct input URI.

- -

Args: - ag (object): Agave object to fetch profiles or metadata. - path (str): The directory path.

- -

Returns: - str: The corresponding input URI.

- -

Raises: - ValueError: If no matching directory pattern is found.

-
- - -
-
- - \ No newline at end of file diff --git a/docs/dsjobs/jobs.html b/docs/dsjobs/jobs.html deleted file mode 100644 index bd977ae..0000000 --- a/docs/dsjobs/jobs.html +++ /dev/null @@ -1,791 +0,0 @@ - - - - - - - dsjobs.jobs API documentation - - - - - - - - - -
-
-

-dsjobs.jobs

- - - - - - -
  1import time
-  2from datetime import datetime, timedelta, timezone
-  3from tqdm import tqdm
-  4import logging
-  5
-  6# Configuring the logging system
-  7# logging.basicConfig(
-  8#     level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
-  9# )
- 10
- 11
- 12def get_status(ag, job_id, time_lapse=15):
- 13    """
- 14    Retrieves and monitors the status of a job from Agave.
- 15
- 16    This function initially waits for the job to start, displaying its progress using
- 17    a tqdm progress bar. Once the job starts, it monitors the job's status up to
- 18    a maximum duration specified by the job's "maxHours". If the job completes or fails
- 19    before reaching this maximum duration, it returns the job's final status.
- 20
- 21    Args:
- 22      ag (object): The Agave job object used to interact with the job.
- 23      job_id (str): The unique identifier of the job to monitor.
- 24      time_lapse (int, optional): Time interval, in seconds, to wait between status
- 25        checks. Defaults to 15 seconds.
- 26
- 27    Returns:
- 28      str: The final status of the job. Typical values include "FINISHED", "FAILED",
- 29           and "STOPPED".
- 30
- 31    Raises:
- 32      No exceptions are explicitly raised, but potential exceptions raised by the Agave
- 33      job object or other called functions/methods will propagate.
- 34    """
- 35
- 36    previous_status = None
- 37    # Initially check if the job is already running
- 38    status = ag.jobs.getStatus(jobId=job_id)["status"]
- 39
- 40    job_details = ag.jobs.get(jobId=job_id)
- 41    max_hours = job_details["maxHours"]
- 42
- 43    # Using tqdm to provide visual feedback while waiting for job to start
- 44    with tqdm(desc="Waiting for job to start", dynamic_ncols=True) as pbar:
- 45        while status not in ["RUNNING", "FINISHED", "FAILED", "STOPPED"]:
- 46            time.sleep(time_lapse)
- 47            status = ag.jobs.getStatus(jobId=job_id)["status"]
- 48            pbar.update(1)
- 49            pbar.set_postfix_str(f"Status: {status}")
- 50
- 51    # Once the job is running, monitor it for up to maxHours
- 52    max_iterations = int(max_hours * 3600 // time_lapse)
- 53
- 54    # Using tqdm for progress bar
- 55    for _ in tqdm(range(max_iterations), desc="Monitoring job", ncols=100):
- 56        status = ag.jobs.getStatus(jobId=job_id)["status"]
- 57
- 58        # Print status if it has changed
- 59        if status != previous_status:
- 60            tqdm.write(f"\tStatus: {status}")
- 61            previous_status = status
- 62
- 63        # Break the loop if job reaches one of these statuses
- 64        if status in ["FINISHED", "FAILED", "STOPPED"]:
- 65            break
- 66
- 67        time.sleep(time_lapse)
- 68    else:
- 69        # This block will execute if the for loop completes without a 'break'
- 70        logging.warn("Warning: Maximum monitoring time reached!")
- 71
- 72    return status
- 73
- 74
- 75def runtime_summary(ag, job_id, verbose=False):
- 76    """Get the runtime of a job.
- 77
- 78    Args:
- 79        ag (object): The Agave object that has the job details.
- 80        job_id (str): The ID of the job for which the runtime needs to be determined.
- 81        verbose (bool): If True, prints all statuses. Otherwise, prints only specific statuses.
- 82
- 83    Returns:
- 84        None: This function doesn't return a value, but it prints the runtime details.
- 85
- 86    """
- 87
- 88    print("Runtime Summary")
- 89    print("---------------")
- 90
- 91    job_history = ag.jobs.getHistory(jobId=job_id)
- 92    total_time = job_history[-1]["created"] - job_history[0]["created"]
- 93
- 94    status_times = {}
- 95
- 96    for i in range(len(job_history) - 1):
- 97        current_status = job_history[i]["status"]
- 98        elapsed_time = job_history[i + 1]["created"] - job_history[i]["created"]
- 99
-100        # Aggregate times for each status
-101        if current_status in status_times:
-102            status_times[current_status] += elapsed_time
-103        else:
-104            status_times[current_status] = elapsed_time
-105
-106    # Filter the statuses if verbose is False
-107    if not verbose:
-108        filtered_statuses = {
-109            "PENDING",
-110            "QUEUED",
-111            "RUNNING",
-112            "FINISHED",
-113            "FAILED",
-114        }
-115        status_times = {
-116            status: time
-117            for status, time in status_times.items()
-118            if status in filtered_statuses
-119        }
-120
-121    # Determine the max width of status names for alignment
-122    max_status_width = max(len(status) for status in status_times.keys())
-123
-124    # Print the aggregated times for each unique status in a table format
-125    for status, time in status_times.items():
-126        print(f"{status.upper():<{max_status_width + 2}} time: {time}")
-127
-128    print(f"{'TOTAL':<{max_status_width + 2}} time: {total_time}")
-129    print("---------------")
-130
-131
-132def generate_job_info(
-133    ag,
-134    appid: str,
-135    jobname: str = "dsjob",
-136    queue: str = "skx-dev",
-137    nnodes: int = 1,
-138    nprocessors: int = 1,
-139    runtime: str = "00:10:00",
-140    inputs=None,
-141    parameters=None,
-142) -> dict:
-143    """Generate a job information dictionary based on provided arguments.
-144
-145    Args:
-146        ag (object): The Agave object to interact with the platform.
-147        appid (str): The application ID for the job.
-148        jobname (str, optional): The name of the job. Defaults to 'dsjob'.
-149        queue (str, optional): The batch queue name. Defaults to 'skx-dev'.
-150        nnodes (int, optional): The number of nodes required. Defaults to 1.
-151        nprocessors (int, optional): The number of processors per node. Defaults to 1.
-152        runtime (str, optional): The maximum runtime in the format 'HH:MM:SS'. Defaults to '00:10:00'.
-153        inputs (dict, optional): The inputs for the job. Defaults to None.
-154        parameters (dict, optional): The parameters for the job. Defaults to None.
-155
-156    Returns:
-157        dict: A dictionary containing the job information.
-158
-159    Raises:
-160        ValueError: If the provided appid is not valid.
-161    """
-162
-163    try:
-164        app = ag.apps.get(appId=appid)
-165    except Exception:
-166        raise ValueError(f"Invalid app ID: {appid}")
-167
-168    job_info = {
-169        "appId": appid,
-170        "name": jobname,
-171        "batchQueue": queue,
-172        "nodeCount": nnodes,
-173        "processorsPerNode": nprocessors,
-174        "memoryPerNode": "1",
-175        "maxRunTime": runtime,
-176        "archive": True,
-177        "inputs": inputs,
-178        "parameters": parameters,
-179    }
-180
-181    return job_info
-182
-183
-184def get_archive_path(ag, job_id):
-185    """
-186    Get the archive path for a given job ID and modifies the user directory
-187    to '/home/jupyter/MyData'.
-188
-189    Args:
-190        ag (object): The Agave object to interact with the platform.
-191        job_id (str): The job ID to retrieve the archive path for.
-192
-193    Returns:
-194        str: The modified archive path.
-195
-196    Raises:
-197        ValueError: If the archivePath format is unexpected.
-198    """
-199
-200    # Fetch the job info.
-201    job_info = ag.jobs.get(jobId=job_id)
-202
-203    # Try to split the archive path to extract the user.
-204    try:
-205        user, _ = job_info.archivePath.split("/", 1)
-206    except ValueError:
-207        raise ValueError(f"Unexpected archivePath format for jobId={job_id}")
-208
-209    # Construct the new path.
-210    new_path = job_info.archivePath.replace(user, "/home/jupyter/MyData")
-211
-212    return new_path
-
- - -
-
- -
- - def - get_status(ag, job_id, time_lapse=15): - - - -
- -
13def get_status(ag, job_id, time_lapse=15):
-14    """
-15    Retrieves and monitors the status of a job from Agave.
-16
-17    This function initially waits for the job to start, displaying its progress using
-18    a tqdm progress bar. Once the job starts, it monitors the job's status up to
-19    a maximum duration specified by the job's "maxHours". If the job completes or fails
-20    before reaching this maximum duration, it returns the job's final status.
-21
-22    Args:
-23      ag (object): The Agave job object used to interact with the job.
-24      job_id (str): The unique identifier of the job to monitor.
-25      time_lapse (int, optional): Time interval, in seconds, to wait between status
-26        checks. Defaults to 15 seconds.
-27
-28    Returns:
-29      str: The final status of the job. Typical values include "FINISHED", "FAILED",
-30           and "STOPPED".
-31
-32    Raises:
-33      No exceptions are explicitly raised, but potential exceptions raised by the Agave
-34      job object or other called functions/methods will propagate.
-35    """
-36
-37    previous_status = None
-38    # Initially check if the job is already running
-39    status = ag.jobs.getStatus(jobId=job_id)["status"]
-40
-41    job_details = ag.jobs.get(jobId=job_id)
-42    max_hours = job_details["maxHours"]
-43
-44    # Using tqdm to provide visual feedback while waiting for job to start
-45    with tqdm(desc="Waiting for job to start", dynamic_ncols=True) as pbar:
-46        while status not in ["RUNNING", "FINISHED", "FAILED", "STOPPED"]:
-47            time.sleep(time_lapse)
-48            status = ag.jobs.getStatus(jobId=job_id)["status"]
-49            pbar.update(1)
-50            pbar.set_postfix_str(f"Status: {status}")
-51
-52    # Once the job is running, monitor it for up to maxHours
-53    max_iterations = int(max_hours * 3600 // time_lapse)
-54
-55    # Using tqdm for progress bar
-56    for _ in tqdm(range(max_iterations), desc="Monitoring job", ncols=100):
-57        status = ag.jobs.getStatus(jobId=job_id)["status"]
-58
-59        # Print status if it has changed
-60        if status != previous_status:
-61            tqdm.write(f"\tStatus: {status}")
-62            previous_status = status
-63
-64        # Break the loop if job reaches one of these statuses
-65        if status in ["FINISHED", "FAILED", "STOPPED"]:
-66            break
-67
-68        time.sleep(time_lapse)
-69    else:
-70        # This block will execute if the for loop completes without a 'break'
-71        logging.warn("Warning: Maximum monitoring time reached!")
-72
-73    return status
-
- - -

Retrieves and monitors the status of a job from Agave.

- -

This function initially waits for the job to start, displaying its progress using -a tqdm progress bar. Once the job starts, it monitors the job's status up to -a maximum duration specified by the job's "maxHours". If the job completes or fails -before reaching this maximum duration, it returns the job's final status.

- -

Args: - ag (object): The Agave job object used to interact with the job. - job_id (str): The unique identifier of the job to monitor. - time_lapse (int, optional): Time interval, in seconds, to wait between status - checks. Defaults to 15 seconds.

- -

Returns: - str: The final status of the job. Typical values include "FINISHED", "FAILED", - and "STOPPED".

- -

Raises: - No exceptions are explicitly raised, but potential exceptions raised by the Agave - job object or other called functions/methods will propagate.

-
- - -
-
- -
- - def - runtime_summary(ag, job_id, verbose=False): - - - -
- -
 76def runtime_summary(ag, job_id, verbose=False):
- 77    """Get the runtime of a job.
- 78
- 79    Args:
- 80        ag (object): The Agave object that has the job details.
- 81        job_id (str): The ID of the job for which the runtime needs to be determined.
- 82        verbose (bool): If True, prints all statuses. Otherwise, prints only specific statuses.
- 83
- 84    Returns:
- 85        None: This function doesn't return a value, but it prints the runtime details.
- 86
- 87    """
- 88
- 89    print("Runtime Summary")
- 90    print("---------------")
- 91
- 92    job_history = ag.jobs.getHistory(jobId=job_id)
- 93    total_time = job_history[-1]["created"] - job_history[0]["created"]
- 94
- 95    status_times = {}
- 96
- 97    for i in range(len(job_history) - 1):
- 98        current_status = job_history[i]["status"]
- 99        elapsed_time = job_history[i + 1]["created"] - job_history[i]["created"]
-100
-101        # Aggregate times for each status
-102        if current_status in status_times:
-103            status_times[current_status] += elapsed_time
-104        else:
-105            status_times[current_status] = elapsed_time
-106
-107    # Filter the statuses if verbose is False
-108    if not verbose:
-109        filtered_statuses = {
-110            "PENDING",
-111            "QUEUED",
-112            "RUNNING",
-113            "FINISHED",
-114            "FAILED",
-115        }
-116        status_times = {
-117            status: time
-118            for status, time in status_times.items()
-119            if status in filtered_statuses
-120        }
-121
-122    # Determine the max width of status names for alignment
-123    max_status_width = max(len(status) for status in status_times.keys())
-124
-125    # Print the aggregated times for each unique status in a table format
-126    for status, time in status_times.items():
-127        print(f"{status.upper():<{max_status_width + 2}} time: {time}")
-128
-129    print(f"{'TOTAL':<{max_status_width + 2}} time: {total_time}")
-130    print("---------------")
-
- - -

Get the runtime of a job.

- -

Args: - ag (object): The Agave object that has the job details. - job_id (str): The ID of the job for which the runtime needs to be determined. - verbose (bool): If True, prints all statuses. Otherwise, prints only specific statuses.

- -

Returns: - None: This function doesn't return a value, but it prints the runtime details.

-
- - -
-
- -
- - def - generate_job_info( ag, appid: str, jobname: str = 'dsjob', queue: str = 'skx-dev', nnodes: int = 1, nprocessors: int = 1, runtime: str = '00:10:00', inputs=None, parameters=None) -> dict: - - - -
- -
133def generate_job_info(
-134    ag,
-135    appid: str,
-136    jobname: str = "dsjob",
-137    queue: str = "skx-dev",
-138    nnodes: int = 1,
-139    nprocessors: int = 1,
-140    runtime: str = "00:10:00",
-141    inputs=None,
-142    parameters=None,
-143) -> dict:
-144    """Generate a job information dictionary based on provided arguments.
-145
-146    Args:
-147        ag (object): The Agave object to interact with the platform.
-148        appid (str): The application ID for the job.
-149        jobname (str, optional): The name of the job. Defaults to 'dsjob'.
-150        queue (str, optional): The batch queue name. Defaults to 'skx-dev'.
-151        nnodes (int, optional): The number of nodes required. Defaults to 1.
-152        nprocessors (int, optional): The number of processors per node. Defaults to 1.
-153        runtime (str, optional): The maximum runtime in the format 'HH:MM:SS'. Defaults to '00:10:00'.
-154        inputs (dict, optional): The inputs for the job. Defaults to None.
-155        parameters (dict, optional): The parameters for the job. Defaults to None.
-156
-157    Returns:
-158        dict: A dictionary containing the job information.
-159
-160    Raises:
-161        ValueError: If the provided appid is not valid.
-162    """
-163
-164    try:
-165        app = ag.apps.get(appId=appid)
-166    except Exception:
-167        raise ValueError(f"Invalid app ID: {appid}")
-168
-169    job_info = {
-170        "appId": appid,
-171        "name": jobname,
-172        "batchQueue": queue,
-173        "nodeCount": nnodes,
-174        "processorsPerNode": nprocessors,
-175        "memoryPerNode": "1",
-176        "maxRunTime": runtime,
-177        "archive": True,
-178        "inputs": inputs,
-179        "parameters": parameters,
-180    }
-181
-182    return job_info
-
- - -

Generate a job information dictionary based on provided arguments.

- -

Args: - ag (object): The Agave object to interact with the platform. - appid (str): The application ID for the job. - jobname (str, optional): The name of the job. Defaults to 'dsjob'. - queue (str, optional): The batch queue name. Defaults to 'skx-dev'. - nnodes (int, optional): The number of nodes required. Defaults to 1. - nprocessors (int, optional): The number of processors per node. Defaults to 1. - runtime (str, optional): The maximum runtime in the format 'HH:MM:SS'. Defaults to '00:10:00'. - inputs (dict, optional): The inputs for the job. Defaults to None. - parameters (dict, optional): The parameters for the job. Defaults to None.

- -

Returns: - dict: A dictionary containing the job information.

- -

Raises: - ValueError: If the provided appid is not valid.

-
- - -
-
- -
- - def - get_archive_path(ag, job_id): - - - -
- -
185def get_archive_path(ag, job_id):
-186    """
-187    Get the archive path for a given job ID and modifies the user directory
-188    to '/home/jupyter/MyData'.
-189
-190    Args:
-191        ag (object): The Agave object to interact with the platform.
-192        job_id (str): The job ID to retrieve the archive path for.
-193
-194    Returns:
-195        str: The modified archive path.
-196
-197    Raises:
-198        ValueError: If the archivePath format is unexpected.
-199    """
-200
-201    # Fetch the job info.
-202    job_info = ag.jobs.get(jobId=job_id)
-203
-204    # Try to split the archive path to extract the user.
-205    try:
-206        user, _ = job_info.archivePath.split("/", 1)
-207    except ValueError:
-208        raise ValueError(f"Unexpected archivePath format for jobId={job_id}")
-209
-210    # Construct the new path.
-211    new_path = job_info.archivePath.replace(user, "/home/jupyter/MyData")
-212
-213    return new_path
-
- - -

Get the archive path for a given job ID and modifies the user directory -to '/home/jupyter/MyData'.

- -

Args: - ag (object): The Agave object to interact with the platform. - job_id (str): The job ID to retrieve the archive path for.

- -

Returns: - str: The modified archive path.

- -

Raises: - ValueError: If the archivePath format is unexpected.

-
- - -
-
- - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 5bd6d3d..0000000 --- a/docs/index.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/docs/search.js b/docs/search.js deleted file mode 100644 index 429e0b3..0000000 --- a/docs/search.js +++ /dev/null @@ -1,46 +0,0 @@ -window.pdocSearch = (function(){ -/** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();odsjobs is a library that simplifies the process of submitting, running, and monitoring TAPIS v2 / AgavePy jobs on DesignSafe via Jupyter Notebooks.

\n\n

Features

\n\n
    \n
  • Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. dsjobs abstracts away the complexities.

  • \n
  • Seamless Integration with DesignSafe Jupyter Notebooks: Launch DesignSafe applications directly from the Jupyter environment.

  • \n
\n\n

Installation

\n\n
\n
pip3 install dsjobs\n
\n
\n"}, "dsjobs.dir": {"fullname": "dsjobs.dir", "modulename": "dsjobs.dir", "kind": "module", "doc": "

\n"}, "dsjobs.dir.get_ds_path_uri": {"fullname": "dsjobs.dir.get_ds_path_uri", "modulename": "dsjobs.dir", "qualname": "get_ds_path_uri", "kind": "function", "doc": "

Given a path on DesignSafe, determine the correct input URI.

\n\n

Args:\n ag (object): Agave object to fetch profiles or metadata.\n path (str): The directory path.

\n\n

Returns:\n str: The corresponding input URI.

\n\n

Raises:\n ValueError: If no matching directory pattern is found.

\n", "signature": "(ag, path):", "funcdef": "def"}, "dsjobs.jobs": {"fullname": "dsjobs.jobs", "modulename": "dsjobs.jobs", "kind": "module", "doc": "

\n"}, "dsjobs.jobs.get_status": {"fullname": "dsjobs.jobs.get_status", "modulename": "dsjobs.jobs", "qualname": "get_status", "kind": "function", "doc": "

Retrieves and monitors the status of a job from Agave.

\n\n

This function initially waits for the job to start, displaying its progress using\na tqdm progress bar. Once the job starts, it monitors the job's status up to\na maximum duration specified by the job's \"maxHours\". If the job completes or fails\nbefore reaching this maximum duration, it returns the job's final status.

\n\n

Args:\n ag (object): The Agave job object used to interact with the job.\n job_id (str): The unique identifier of the job to monitor.\n time_lapse (int, optional): Time interval, in seconds, to wait between status\n checks. Defaults to 15 seconds.

\n\n

Returns:\n str: The final status of the job. Typical values include \"FINISHED\", \"FAILED\",\n and \"STOPPED\".

\n\n

Raises:\n No exceptions are explicitly raised, but potential exceptions raised by the Agave\n job object or other called functions/methods will propagate.

\n", "signature": "(ag, job_id, time_lapse=15):", "funcdef": "def"}, "dsjobs.jobs.runtime_summary": {"fullname": "dsjobs.jobs.runtime_summary", "modulename": "dsjobs.jobs", "qualname": "runtime_summary", "kind": "function", "doc": "

Get the runtime of a job.

\n\n

Args:\n ag (object): The Agave object that has the job details.\n job_id (str): The ID of the job for which the runtime needs to be determined.\n verbose (bool): If True, prints all statuses. Otherwise, prints only specific statuses.

\n\n

Returns:\n None: This function doesn't return a value, but it prints the runtime details.

\n", "signature": "(ag, job_id, verbose=False):", "funcdef": "def"}, "dsjobs.jobs.generate_job_info": {"fullname": "dsjobs.jobs.generate_job_info", "modulename": "dsjobs.jobs", "qualname": "generate_job_info", "kind": "function", "doc": "

Generate a job information dictionary based on provided arguments.

\n\n

Args:\n ag (object): The Agave object to interact with the platform.\n appid (str): The application ID for the job.\n jobname (str, optional): The name of the job. Defaults to 'dsjob'.\n queue (str, optional): The batch queue name. Defaults to 'skx-dev'.\n nnodes (int, optional): The number of nodes required. Defaults to 1.\n nprocessors (int, optional): The number of processors per node. Defaults to 1.\n runtime (str, optional): The maximum runtime in the format 'HH:MM:SS'. Defaults to '00:10:00'.\n inputs (dict, optional): The inputs for the job. Defaults to None.\n parameters (dict, optional): The parameters for the job. Defaults to None.

\n\n

Returns:\n dict: A dictionary containing the job information.

\n\n

Raises:\n ValueError: If the provided appid is not valid.

\n", "signature": "(\tag,\tappid: str,\tjobname: str = 'dsjob',\tqueue: str = 'skx-dev',\tnnodes: int = 1,\tnprocessors: int = 1,\truntime: str = '00:10:00',\tinputs=None,\tparameters=None) -> dict:", "funcdef": "def"}, "dsjobs.jobs.get_archive_path": {"fullname": "dsjobs.jobs.get_archive_path", "modulename": "dsjobs.jobs", "qualname": "get_archive_path", "kind": "function", "doc": "

Get the archive path for a given job ID and modifies the user directory\nto '/home/jupyter/MyData'.

\n\n

Args:\n ag (object): The Agave object to interact with the platform.\n job_id (str): The job ID to retrieve the archive path for.

\n\n

Returns:\n str: The modified archive path.

\n\n

Raises:\n ValueError: If the archivePath format is unexpected.

\n", "signature": "(ag, job_id):", "funcdef": "def"}}, "docInfo": {"dsjobs": {"qualname": 0, "fullname": 1, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 105}, "dsjobs.dir": {"qualname": 0, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "dsjobs.dir.get_ds_path_uri": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 16, "bases": 0, "doc": 54}, "dsjobs.jobs": {"qualname": 0, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "dsjobs.jobs.get_status": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 28, "bases": 0, "doc": 161}, "dsjobs.jobs.runtime_summary": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 27, "bases": 0, "doc": 72}, "dsjobs.jobs.generate_job_info": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 151, "bases": 0, "doc": 146}, "dsjobs.jobs.get_archive_path": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 17, "bases": 0, "doc": 67}}, "length": 8, "save": true}, "index": {"qualname": {"root": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 3}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}}}, "d": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 2}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}}}}, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}}}}, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "o": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 1}}}}}}}}}, "fullname": {"root": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs": {"tf": 1}, "dsjobs.dir": {"tf": 1}, "dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 8}}}}}, "i": {"docs": {}, "df": 0, "r": {"docs": {"dsjobs.dir": {"tf": 1}, "dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 2}}}, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 3}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 2}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1}}}, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1, "s": {"docs": {"dsjobs.jobs": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 5}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}}}}, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "o": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 1}}}}}}}}}, "annotation": {"root": {"docs": {}, "df": 0}}, "default_value": {"root": {"docs": {}, "df": 0}}, "signature": {"root": {"0": {"0": {"docs": {}, "df": 0, ":": {"1": {"0": {"docs": {}, "df": 0, ":": {"0": {"0": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}, "docs": {}, "df": 0}, "docs": {}, "df": 0}}, "docs": {}, "df": 0}, "docs": {}, "df": 0}}, "docs": {}, "df": 0}, "1": {"5": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}, "docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 1}, "3": {"9": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 2.449489742783178}}, "df": 1}, "docs": {}, "df": 0}, "docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 3.7416573867739413}, "dsjobs.jobs.get_status": {"tf": 4.69041575982343}, "dsjobs.jobs.runtime_summary": {"tf": 4.69041575982343}, "dsjobs.jobs.generate_job_info": {"tf": 11}, "dsjobs.jobs.get_archive_path": {"tf": 3.7416573867739413}}, "df": 5, "a": {"docs": {}, "df": 0, "g": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 5}, "p": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1}}, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}}}}}, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 3, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}}, "i": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 3}, "n": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 1}, "p": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}}}}, "f": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 2}}, "df": 1}}, "k": {"docs": {}, "df": 0, "x": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}, "d": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}, "e": {"docs": {}, "df": 0, "v": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}}}}}, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 1}}}}, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}}}}, "bases": {"root": {"docs": {}, "df": 0}}, "doc": {"root": {"0": {"0": {"docs": {}, "df": 0, ":": {"1": {"0": {"docs": {}, "df": 0, ":": {"0": {"0": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}, "docs": {}, "df": 0}, "docs": {}, "df": 0}}, "docs": {}, "df": 0}, "docs": {}, "df": 0}}, "docs": {}, "df": 0}, "1": {"5": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}, "docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 1}, "docs": {"dsjobs": {"tf": 6.855654600401044}, "dsjobs.dir": {"tf": 1.7320508075688772}, "dsjobs.dir.get_ds_path_uri": {"tf": 3.7416573867739413}, "dsjobs.jobs": {"tf": 1.7320508075688772}, "dsjobs.jobs.get_status": {"tf": 4.242640687119285}, "dsjobs.jobs.runtime_summary": {"tf": 3.4641016151377544}, "dsjobs.jobs.generate_job_info": {"tf": 4.58257569495584}, "dsjobs.jobs.get_archive_path": {"tf": 3.7416573867739413}}, "df": 8, "d": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1, "s": {"docs": {"dsjobs": {"tf": 1.7320508075688772}}, "df": 1}}}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs": {"tf": 1.7320508075688772}, "dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 2}}}}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1, "d": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}}}}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "f": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 2.6457513110645907}}, "df": 2}}}}}}, "v": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}, "i": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1.4142135623730951}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 2}}}}}}}, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}}}, "c": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.7320508075688772}}, "df": 1, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"dsjobs.jobs.get_status": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}, "o": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "n": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}}, "i": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs": {"tf": 1}, "dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 4}, "n": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 2, "t": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 2, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}}}}, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 3}}}, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"dsjobs": {"tf": 1}}, "df": 1, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}}}}}}}, "p": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1.4142135623730951}}, "df": 1, "s": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}}, "c": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}, "f": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}}, "f": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 5}, "t": {"docs": {"dsjobs.jobs.get_status": {"tf": 1.4142135623730951}, "dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 2, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}, "d": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.runtime_summary": {"tf": 1.4142135623730951}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1.7320508075688772}}, "df": 4, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}}}}}, "a": {"docs": {"dsjobs": {"tf": 1}, "dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1.7320508075688772}, "dsjobs.jobs.runtime_summary": {"tf": 1.4142135623730951}, "dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 6, "n": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1.4142135623730951}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 3}}, "g": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 5, "a": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1.7320508075688772}, "dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 5, "p": {"docs": {}, "df": 0, "y": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}}}, "p": {"docs": {}, "df": 0, "i": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1, "s": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}}}}}}, "i": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 1}}}}, "b": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}}}}}, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "y": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 5}, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.get_archive_path": {"tf": 1.7320508075688772}}, "df": 1, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 1}}}}}}}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}}}, "a": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}, "p": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}, "t": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs": {"tf": 1}, "dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 2}}, "e": {"docs": {"dsjobs": {"tf": 1.7320508075688772}, "dsjobs.dir.get_ds_path_uri": {"tf": 1.7320508075688772}, "dsjobs.jobs.get_status": {"tf": 3.7416573867739413}, "dsjobs.jobs.runtime_summary": {"tf": 2.6457513110645907}, "dsjobs.jobs.generate_job_info": {"tf": 4.123105625617661}, "dsjobs.jobs.get_archive_path": {"tf": 2.8284271247461903}}, "df": 6}, "i": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1.4142135623730951}, "dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 2}}}, "a": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs": {"tf": 1.4142135623730951}}, "df": 1}}}}, "o": {"docs": {"dsjobs": {"tf": 1}, "dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 2.449489742783178}, "dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 2.8284271247461903}, "dsjobs.jobs.get_archive_path": {"tf": 1.7320508075688772}}, "df": 6}, "q": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "m": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.get_status": {"tf": 1.4142135623730951}}, "df": 1}}}, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1.7320508075688772}}, "df": 1, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}, "d": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}}}}}}, "u": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}}}}}}, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}}}, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1.4142135623730951}}, "df": 1}}}}}}, "t": {"docs": {}, "df": 0, "r": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1.4142135623730951}, "dsjobs.jobs.get_status": {"tf": 1.4142135623730951}, "dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 2}, "dsjobs.jobs.get_archive_path": {"tf": 1.4142135623730951}}, "df": 5}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 2.23606797749979}}, "df": 1, "e": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "r": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}, "c": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}}}}, "k": {"docs": {}, "df": 0, "x": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs": {"tf": 1}}, "df": 1, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}}, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1}}}}}, "g": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 1}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1.7320508075688772}}, "df": 1}}}}}, "i": {"docs": {}, "df": 0, "p": {"3": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}, "docs": {}, "df": 0}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1.7320508075688772}, "dsjobs.jobs.get_archive_path": {"tf": 1.7320508075688772}}, "df": 2}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "n": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1}}}}}, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}, "o": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}}}, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "m": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 2}}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}, "o": {"docs": {}, "df": 0, "f": {"docs": {"dsjobs": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1.7320508075688772}, "dsjobs.jobs.runtime_summary": {"tf": 1.4142135623730951}, "dsjobs.jobs.generate_job_info": {"tf": 1.7320508075688772}}, "df": 4}, "n": {"docs": {"dsjobs": {"tf": 1}, "dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 3, "c": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}, "l": {"docs": {}, "df": 0, "y": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}, "b": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1.4142135623730951}, "dsjobs.jobs.get_status": {"tf": 1.7320508075688772}, "dsjobs.jobs.runtime_summary": {"tf": 1.4142135623730951}, "dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}, "dsjobs.jobs.get_archive_path": {"tf": 1.4142135623730951}}, "df": 5}}}}}, "r": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1.4142135623730951}}, "df": 2}, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 2.6457513110645907}}, "df": 2}}}}}}}, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1, "w": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}}}}}}, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1.7320508075688772}, "dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 2}}}}}}, "e": {"docs": {}, "df": 0, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "n": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1, "s": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1.4142135623730951}, "dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 5}}}}, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 1, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}}, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 4}, "d": {"docs": {"dsjobs.jobs.get_status": {"tf": 1.4142135623730951}}, "df": 1}}}}}}, "m": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1.4142135623730951}}, "df": 1}}}}}}, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 1}, "d": {"docs": {"dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1}}}}}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1}}}}}}, "x": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "m": {"docs": {"dsjobs.jobs.get_status": {"tf": 1.4142135623730951}, "dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 2}}}}, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}}}, "v": {"2": {"docs": {"dsjobs": {"tf": 1.4142135623730951}}, "df": 1}, "docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 3}}}}}, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}, "i": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}}}}, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {"dsjobs.jobs.get_status": {"tf": 3.605551275463989}, "dsjobs.jobs.runtime_summary": {"tf": 2}, "dsjobs.jobs.generate_job_info": {"tf": 2.449489742783178}, "dsjobs.jobs.get_archive_path": {"tf": 1.7320508075688772}}, "df": 4, "s": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}, "u": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"dsjobs": {"tf": 1.7320508075688772}}, "df": 1}}}}}}}, "n": {"docs": {}, "df": 0, "o": {"docs": {"dsjobs": {"tf": 1}, "dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1}}, "df": 3, "t": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}, "n": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 2}}, "d": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1, "s": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}, "e": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs": {"tf": 1}}, "df": 1, "s": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 1}}}, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}}}}}}, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}}}, "t": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"dsjobs.jobs.get_status": {"tf": 1.4142135623730951}}, "df": 1}}, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {"dsjobs": {"tf": 1}, "dsjobs.jobs.get_status": {"tf": 1}}, "df": 2}}}, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1}}}, "r": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1.7320508075688772}, "dsjobs.jobs.get_archive_path": {"tf": 1.4142135623730951}}, "df": 4, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 2}}}}}, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 2, "s": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}}}}}}}}}}}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}, "e": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}, "e": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "x": {"docs": {"dsjobs": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}}, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1}}, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}}, "df": 1}}}}}}}}}}}, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}}}}, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}, "w": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"dsjobs": {"tf": 1.4142135623730951}, "dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.generate_job_info": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 4}}, "l": {"docs": {}, "df": 0, "l": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}}, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs": {"tf": 1}}, "df": 1}}}}}}}}}}, "x": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.get_status": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}}}}}, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 2}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}, "dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 2}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {"dsjobs.dir.get_ds_path_uri": {"tf": 1.4142135623730951}}, "df": 1}}, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}, "e": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}, "r": {"docs": {"dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 1}}}, "p": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 1}}}}}}}}}}, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}, "t": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}, "y": {"docs": {"dsjobs.jobs.get_status": {"tf": 1.4142135623730951}}, "df": 1}, "e": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1, "f": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}, "t": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}}, "df": 1}}}}}}, "u": {"docs": {}, "df": 0, "t": {"docs": {"dsjobs.jobs.get_status": {"tf": 1}, "dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 2}}, "o": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.runtime_summary": {"tf": 1}}, "df": 1}}, "h": {"docs": {}, "df": 0, ":": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, ":": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1}}, "df": 1}}}}}}}, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {"dsjobs.jobs.get_archive_path": {"tf": 1}}, "df": 1}}}}}}}}}}}}}}}}}}}, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"dsjobs.jobs.generate_job_info": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}, "pipeline": ["trimmer"], "_isPrebuiltIndex": true}; - - // mirrored in build-search-index.js (part 1) - // Also split on html tags. this is a cheap heuristic, but good enough. - elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/); - - let searchIndex; - if (docs._isPrebuiltIndex) { - console.info("using precompiled search index"); - searchIndex = elasticlunr.Index.load(docs); - } else { - console.time("building search index"); - // mirrored in build-search-index.js (part 2) - searchIndex = elasticlunr(function () { - this.pipeline.remove(elasticlunr.stemmer); - this.pipeline.remove(elasticlunr.stopWordFilter); - this.addField("qualname"); - this.addField("fullname"); - this.addField("annotation"); - this.addField("default_value"); - this.addField("signature"); - this.addField("bases"); - this.addField("doc"); - this.setRef("fullname"); - }); - for (let doc of docs) { - searchIndex.addDoc(doc); - } - console.timeEnd("building search index"); - } - - return (term) => searchIndex.search(term, { - fields: { - qualname: {boost: 4}, - fullname: {boost: 2}, - annotation: {boost: 2}, - default_value: {boost: 2}, - signature: {boost: 2}, - bases: {boost: 2}, - doc: {boost: 1}, - }, - expand: true - }); -})(); \ No newline at end of file diff --git a/example-notebooks/mpm/papermill-mpm.ipynb b/examples/mpm/papermill-mpm.ipynb similarity index 100% rename from example-notebooks/mpm/papermill-mpm.ipynb rename to examples/mpm/papermill-mpm.ipynb diff --git a/example-notebooks/mpm/template-mpm-run.ipynb b/examples/mpm/template-mpm-run.ipynb similarity index 98% rename from example-notebooks/mpm/template-mpm-run.ipynb rename to examples/mpm/template-mpm-run.ipynb index 844e1b9..2e806b3 100644 --- a/example-notebooks/mpm/template-mpm-run.ipynb +++ b/examples/mpm/template-mpm-run.ipynb @@ -13,7 +13,7 @@ "id": "abe5d8a3-32da-4233-b605-9fd51d053ec1", "metadata": {}, "source": [ - "## Install DSJobs" + "## Install dapi" ] }, { @@ -33,7 +33,7 @@ } ], "source": [ - "!pip install dsjobs --quiet" + "!pip install dapi --quiet" ] }, { @@ -83,7 +83,7 @@ "outputs": [], "source": [ "from agavepy.agave import Agave\n", - "import dsjobs as ds\n", + "import dapi as ds\n", "import json\n", "import os" ] diff --git a/pyproject.toml b/pyproject.toml index e9a2723..df25ec6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,13 @@ [tool.poetry] -name = "dsjobs" +name = "dapi" version = "0.1.5" -description = "DSJobs simplifies accessing TAPIS on DesignSafe" +description = "dapi simplifies accessing TAPIS on DesignSafe" authors = [ "Krishna Kumar ", "Pedro Arduino " ] readme = "README.md" -packages = [{include = "dsjobs"}] +packages = [{include = "dapi"}] [tool.poetry.dependencies] python = "^3.8" diff --git a/tests/__init__.py b/tests/jobs/__init__.py similarity index 100% rename from tests/__init__.py rename to tests/jobs/__init__.py diff --git a/tests/test_dir_uri.py b/tests/jobs/test_dir_uri.py similarity index 98% rename from tests/test_dir_uri.py rename to tests/jobs/test_dir_uri.py index 3a26d4e..7795210 100644 --- a/tests/test_dir_uri.py +++ b/tests/jobs/test_dir_uri.py @@ -1,7 +1,7 @@ import unittest from unittest.mock import MagicMock, patch -from dsjobs import get_ds_path_uri +from dapi.jobs import get_ds_path_uri class TestGetDsPathUri(unittest.TestCase): diff --git a/tests/test_job_gen_jobinfo.py b/tests/jobs/test_job_gen_jobinfo.py similarity index 98% rename from tests/test_job_gen_jobinfo.py rename to tests/jobs/test_job_gen_jobinfo.py index 96411fc..4b3779d 100644 --- a/tests/test_job_gen_jobinfo.py +++ b/tests/jobs/test_job_gen_jobinfo.py @@ -1,6 +1,6 @@ import unittest from unittest.mock import Mock, patch -from dsjobs import jobs +from dapi.jobs import jobs class TestGenerateJobInfo(unittest.TestCase): diff --git a/tests/test_job_get_archive_path.py b/tests/jobs/test_job_get_archive_path.py similarity index 96% rename from tests/test_job_get_archive_path.py rename to tests/jobs/test_job_get_archive_path.py index f1a5000..7f2da79 100644 --- a/tests/test_job_get_archive_path.py +++ b/tests/jobs/test_job_get_archive_path.py @@ -1,6 +1,6 @@ import unittest from unittest.mock import patch, Mock -from dsjobs import get_archive_path +from dapi.jobs import get_archive_path class TestGetArchivePath(unittest.TestCase): diff --git a/tests/test_job_runtime.py b/tests/jobs/test_job_runtime.py similarity index 97% rename from tests/test_job_runtime.py rename to tests/jobs/test_job_runtime.py index 893c7f3..9be9f53 100644 --- a/tests/test_job_runtime.py +++ b/tests/jobs/test_job_runtime.py @@ -4,7 +4,7 @@ import sys from datetime import datetime, timedelta -import dsjobs as ds +import dapi as ds class TestRuntimeSummary(unittest.TestCase): @@ -59,7 +59,7 @@ def capture_output(self, ag_mock, job_id, verbose): try: out = StringIO() sys.stdout = out - ds.runtime_summary(ag_mock, job_id, verbose) + ds.jobs.runtime_summary(ag_mock, job_id, verbose) return out.getvalue().strip() finally: sys.stdout = saved_stdout diff --git a/tests/test_job_status.py b/tests/jobs/test_job_status.py similarity index 92% rename from tests/test_job_status.py rename to tests/jobs/test_job_status.py index 1921195..542a045 100644 --- a/tests/test_job_status.py +++ b/tests/jobs/test_job_status.py @@ -1,7 +1,7 @@ import time import unittest from unittest.mock import Mock, patch -import dsjobs as ds +import dapi as ds class TestGetStatus(unittest.TestCase): @@ -24,7 +24,7 @@ def test_get_status(self): mock_agave.jobs.get.return_value = {"maxHours": 0.01} # Call get_status - status = ds.get_status(mock_agave, "some_job_id", time_lapse=1) + status = ds.jobs.get_status(mock_agave, "some_job_id", time_lapse=1) # Assert that the final status is "FINISHED" self.assertEqual(status, "FINISHED")