Skip to content

Commit

Permalink
Merge pull request #1 from DesignSafe-CI/tapisv3
Browse files Browse the repository at this point in the history
Tapisv3
  • Loading branch information
kks32 authored Nov 22, 2024
2 parents cb4471d + d09b970 commit 4fd4ddc
Show file tree
Hide file tree
Showing 42 changed files with 13,397 additions and 2,265 deletions.
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
[![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/dapi/dapi/index.html)

`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).
`dapi` is a library that simplifies the process of submitting, running, and monitoring [TAPIS v3](https://tapis.readthedocs.io/en/latest/) jobs on [DesignSafe](https://designsafe-ci.org) via [Jupyter Notebooks](https://jupyter.designsafe-ci.org).

## Features

### Jobs

* Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. `dapi` abstracts away the complexities.
* Get TAPIS v3 templates for jobs: 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.

Expand Down Expand Up @@ -53,6 +53,15 @@ pip install git+https://github.com/DesignSafe-CI/dapi.git --quiet

## Example usage:

### Storing credentials

Dapi uses the Tapis v3 SDK to authenticate with the DesignSafe API. To store your credentials, create a `.env` file in the root of your project with the following content:

```shell
DESIGNSAFE_USERNAME=<your_designsafe_username>
DESIGNSAFE_PASSWORD=<your_designsafe_password>
```

### Jobs

* [Jupyter Notebook Templates](example-notebooks/template-mpm-run.ipynb) using dapi.
Expand All @@ -66,7 +75,7 @@ Install the latest version of `dapi` and restart the kernel (Kernel >> Restart K
```python
# Remove any previous installations
!pip uninstall dapi -y
# Install
# Install
!pip install dapi --quiet
```

Expand Down Expand Up @@ -122,10 +131,6 @@ To run the unit test
poetry run pytest -v
```

## Known Issues

The project only works on `Python 3.9` due to AgavePy Issue [#125](https://github.com/TACC/agavepy/issues/125).


## License

Expand Down
28 changes: 25 additions & 3 deletions dapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
"""
`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).
dapi` is a library that simplifies the process of submitting, running, and monitoring [TAPIS v3](https://tapis.readthedocs.io/en/latest/) 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. `dapi` abstracts away the complexities.
### Jobs
* Get TAPIS v3 templates for jobs: 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.
### Database
Connects to SQL databases on DesignSafe:
| Database | dbname | env_prefix |
|----------|--------|------------|
| NGL | `ngl`| `NGL_` |
| Earthake Recovery | `eq` | `EQ_` |
| Vp | `vp` | `VP_` |
Define the following environment variables:
```
{env_prefix}DB_USER
{env_prefix}DB_PASSWORD
{env_prefix}DB_HOST
{env_prefix}DB_PORT
```
For e.g., to add the environment variable `NGL_DB_USER` edit `~/.bashrc`, `~/.zshrc`, or a similar shell-specific configuration file for the current user and add `export NGL_DB_USER="dspublic"`.
## Installation
```shell
pip3 install dapi
```
"""
from . import apps
from . import auth
from . import db
from . import jobs
3 changes: 3 additions & 0 deletions dapi/apps/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .apps import find_apps, get_app_version

__all__ = ["find_apps", "get_app_version"]
57 changes: 57 additions & 0 deletions dapi/apps/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from tapipy.tapis import Tapis
from typing import List, Dict, Any, Optional


def find_apps(
t: Tapis, search_term: str, list_type: str = "ALL", verbose: bool = True
) -> List[Any]:
"""
Search for Tapis apps matching a search term.
Args:
t (Tapis): Tapis client instance
search_term (str): Name or partial name to search for
list_type (str): One of 'OWNED', 'SHARED_PUBLIC', 'SHARED_DIRECT', 'READ_PERM', 'MINE', 'ALL'
verbose (bool): If True, prints all found apps
Returns:
List[Any]: List of matching app objects
"""
results = t.apps.getApps(search=f"(id.like.*{search_term}*)", listType=list_type)

if verbose:
if not results:
print(f"No apps found matching '{search_term}'")
else:
print(f"\nFound {len(results)} matching apps:")
for app in results:
print(f"- {app.id}")
print()

return results


def get_app_version(t: Tapis, app_id: str, verbose: bool = True) -> Optional[Any]:
"""
Get latest version info for a specific app ID.
Args:
t (Tapis): Tapis client instance
app_id (str): Exact app ID to look up
verbose (bool): If True, prints basic app info
Returns:
Optional[Any]: Latest version info for the app, or None if not found
"""
try:
app_info = t.apps.getAppLatestVersion(appId=app_id)
if verbose:
print(f"App: {app_info.id}")
print(f"Version: {app_info.version}")
print(f"System: {app_info.jobAttributes.execSystemId}")
return app_info
except Exception as e:
print(f"Error getting app info for '{app_id}': {str(e)}")
print("\nCouldn't find exact match. Here are similar apps:")
_ = find_apps(t, app_id)
return None
58 changes: 35 additions & 23 deletions dapi/auth/auth.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
from agavepy.agave import Agave
from collections.abc import Mapping
import os
from getpass import getpass
from tapipy.tapis import Tapis
from dotenv import load_dotenv


def init(username, password):
def init():
"""
Initialize an Agave object with a new client and an active token.
Initialize a Tapis object with authentication.
Tries to read credentials from environment variables first.
If not found, prompts the user for input.
Args:
username (str): The username.
password (str): The password.
Save the user credentials in the .env file.
```
DESIGNSAFE_USERNAME=<username>
DESIGNSAFE_PASSWORD=<password>
```
Returns:
object: The Agave object.
object: The authenticated Tapis object.
"""
# Authenticate with Agave
ag = Agave(
base_url="https://agave.designsafe-ci.org", username=username, password=password
)
# Create a new client
new_client = ag.clients_create()
# create a new ag object with the new client, at this point ag will have a new token
ag = Agave(
base_url="https://agave.designsafe-ci.org",
username=username,
password=password,
api_key=new_client["api_key"],
api_secret=new_client["api_secret"],
)
return ag
base_url = "https://designsafe.tapis.io"

# Load environment variables from .env file
load_dotenv()

# Try to get credentials from environment variables
username = os.getenv("DESIGNSAFE_USERNAME")
password = os.getenv("DESIGNSAFE_PASSWORD")

# If environment variables are not set, prompt user for input
if not username:
username = input("Enter username: ")
if not password:
password = getpass("Enter password: ")

# Initialize Tapis object
t = Tapis(base_url=base_url, username=username, password=password)

t.get_tokens()

return t
8 changes: 4 additions & 4 deletions dapi/jobs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
"""
`dapi` job submodule 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` job submodule simplifies the process of submitting, running, and monitoring [Tapis v3](https://tapis.readthedocs.io/en/latest/) 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. `dapi` abstracts away the complexities.
* Simplified TAPIS v3 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
# Installation
```shell
pip3 install dapi
```
"""
from .dir import get_ds_path_uri
from .jobs import get_status, runtime_summary, generate_job_info, get_archive_path
from .jobs import get_status, runtime_summary, generate_job_info
35 changes: 19 additions & 16 deletions dapi/jobs/dir.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import os
from tapipy.tapis import Tapis


def get_ds_path_uri(ag, path):
def get_ds_path_uri(t: Tapis, path: str) -> str:
"""
Given a path on DesignSafe, determine the correct input URI.
Given a path on DesignSafe, determine the correct input URI for Tapis v3.
Args:
ag (object): Agave object to fetch profiles or metadata.
path (str): The directory path.
t (Tapis): Tapis object to fetch profiles or metadata.
path (str): The directory path.
Returns:
str: The corresponding input URI.
str: The corresponding input URI.
Raises:
ValueError: If no matching directory pattern is found.
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 = [
Expand All @@ -28,9 +28,9 @@ def get_ds_path_uri(ag, path):

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}"
path = path.split(pattern, 1)[1].lstrip("/")
input_dir = f"{t.username}/{path}" if use_username else path
input_uri = f"tapis://{storage}/{input_dir}"
return input_uri.replace(" ", "%20")

project_patterns = [
Expand All @@ -40,12 +40,15 @@ def get_ds_path_uri(ag, path):

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}"
path = path.split(pattern, 1)[1].lstrip("/")
project_id, *rest = path.split("/", 1)
path = rest[0] if rest else ""

# Using Tapis v3 to get project UUID
resp = t.get(f"https://designsafe-ci.org/api/projects/v2/{project_id}")
project_uuid = resp.json()["baseProject"]["uuid"]

input_uri = f"tapis://{prefix}{project_uuid}/{path}"
return input_uri.replace(" ", "%20")

raise ValueError(f"No matching directory pattern found for: {path}")
Loading

0 comments on commit 4fd4ddc

Please sign in to comment.