Skip to content

Commit 4fd4ddc

Browse files
authored
Merge pull request #1 from DesignSafe-CI/tapisv3
Tapisv3
2 parents cb4471d + d09b970 commit 4fd4ddc

42 files changed

Lines changed: 13397 additions & 2265 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.md)
77
[![Docs](https://img.shields.io/badge/view-docs-8A2BE2?color=8A2BE2)](https://designsafe-ci.github.io/dapi/dapi/index.html)
88

9-
`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).
9+
`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).
1010

1111
## Features
1212

1313
### Jobs
1414

15-
* Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. `dapi` abstracts away the complexities.
15+
* Get TAPIS v3 templates for jobs: No need to fiddle with complex API requests. `dapi` abstracts away the complexities.
1616

1717
* Seamless Integration with DesignSafe Jupyter Notebooks: Launch DesignSafe applications directly from the Jupyter environment.
1818

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

5454
## Example usage:
5555

56+
### Storing credentials
57+
58+
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:
59+
60+
```shell
61+
DESIGNSAFE_USERNAME=<your_designsafe_username>
62+
DESIGNSAFE_PASSWORD=<your_designsafe_password>
63+
```
64+
5665
### Jobs
5766

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

@@ -122,10 +131,6 @@ To run the unit test
122131
poetry run pytest -v
123132
```
124133

125-
## Known Issues
126-
127-
The project only works on `Python 3.9` due to AgavePy Issue [#125](https://github.com/TACC/agavepy/issues/125).
128-
129134

130135
## License
131136

dapi/__init__.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,42 @@
11
"""
2-
`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).
3-
2+
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).
43
54
## Features
65
7-
* Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. `dapi` abstracts away the complexities.
6+
### Jobs
7+
8+
* Get TAPIS v3 templates for jobs: No need to fiddle with complex API requests. `dapi` abstracts away the complexities.
89
910
* Seamless Integration with DesignSafe Jupyter Notebooks: Launch DesignSafe applications directly from the Jupyter environment.
1011
12+
### Database
13+
14+
Connects to SQL databases on DesignSafe:
15+
16+
| Database | dbname | env_prefix |
17+
|----------|--------|------------|
18+
| NGL | `ngl`| `NGL_` |
19+
| Earthake Recovery | `eq` | `EQ_` |
20+
| Vp | `vp` | `VP_` |
21+
22+
Define the following environment variables:
23+
```
24+
{env_prefix}DB_USER
25+
{env_prefix}DB_PASSWORD
26+
{env_prefix}DB_HOST
27+
{env_prefix}DB_PORT
28+
```
29+
30+
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"`.
31+
1132
## Installation
1233
1334
```shell
1435
pip3 install dapi
1536
```
1637
1738
"""
39+
from . import apps
1840
from . import auth
1941
from . import db
2042
from . import jobs

dapi/apps/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .apps import find_apps, get_app_version
2+
3+
__all__ = ["find_apps", "get_app_version"]

dapi/apps/apps.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from tapipy.tapis import Tapis
2+
from typing import List, Dict, Any, Optional
3+
4+
5+
def find_apps(
6+
t: Tapis, search_term: str, list_type: str = "ALL", verbose: bool = True
7+
) -> List[Any]:
8+
"""
9+
Search for Tapis apps matching a search term.
10+
11+
Args:
12+
t (Tapis): Tapis client instance
13+
search_term (str): Name or partial name to search for
14+
list_type (str): One of 'OWNED', 'SHARED_PUBLIC', 'SHARED_DIRECT', 'READ_PERM', 'MINE', 'ALL'
15+
verbose (bool): If True, prints all found apps
16+
17+
Returns:
18+
List[Any]: List of matching app objects
19+
"""
20+
results = t.apps.getApps(search=f"(id.like.*{search_term}*)", listType=list_type)
21+
22+
if verbose:
23+
if not results:
24+
print(f"No apps found matching '{search_term}'")
25+
else:
26+
print(f"\nFound {len(results)} matching apps:")
27+
for app in results:
28+
print(f"- {app.id}")
29+
print()
30+
31+
return results
32+
33+
34+
def get_app_version(t: Tapis, app_id: str, verbose: bool = True) -> Optional[Any]:
35+
"""
36+
Get latest version info for a specific app ID.
37+
38+
Args:
39+
t (Tapis): Tapis client instance
40+
app_id (str): Exact app ID to look up
41+
verbose (bool): If True, prints basic app info
42+
43+
Returns:
44+
Optional[Any]: Latest version info for the app, or None if not found
45+
"""
46+
try:
47+
app_info = t.apps.getAppLatestVersion(appId=app_id)
48+
if verbose:
49+
print(f"App: {app_info.id}")
50+
print(f"Version: {app_info.version}")
51+
print(f"System: {app_info.jobAttributes.execSystemId}")
52+
return app_info
53+
except Exception as e:
54+
print(f"Error getting app info for '{app_id}': {str(e)}")
55+
print("\nCouldn't find exact match. Here are similar apps:")
56+
_ = find_apps(t, app_id)
57+
return None

dapi/auth/auth.py

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,42 @@
1-
from agavepy.agave import Agave
2-
from collections.abc import Mapping
1+
import os
2+
from getpass import getpass
3+
from tapipy.tapis import Tapis
4+
from dotenv import load_dotenv
35

46

5-
def init(username, password):
7+
def init():
68
"""
7-
Initialize an Agave object with a new client and an active token.
9+
Initialize a Tapis object with authentication.
10+
Tries to read credentials from environment variables first.
11+
If not found, prompts the user for input.
812
9-
Args:
10-
username (str): The username.
11-
password (str): The password.
13+
Save the user credentials in the .env file.
14+
```
15+
DESIGNSAFE_USERNAME=<username>
16+
DESIGNSAFE_PASSWORD=<password>
17+
```
1218
1319
Returns:
14-
object: The Agave object.
20+
object: The authenticated Tapis object.
1521
"""
16-
# Authenticate with Agave
17-
ag = Agave(
18-
base_url="https://agave.designsafe-ci.org", username=username, password=password
19-
)
20-
# Create a new client
21-
new_client = ag.clients_create()
22-
# create a new ag object with the new client, at this point ag will have a new token
23-
ag = Agave(
24-
base_url="https://agave.designsafe-ci.org",
25-
username=username,
26-
password=password,
27-
api_key=new_client["api_key"],
28-
api_secret=new_client["api_secret"],
29-
)
30-
return ag
22+
base_url = "https://designsafe.tapis.io"
23+
24+
# Load environment variables from .env file
25+
load_dotenv()
26+
27+
# Try to get credentials from environment variables
28+
username = os.getenv("DESIGNSAFE_USERNAME")
29+
password = os.getenv("DESIGNSAFE_PASSWORD")
30+
31+
# If environment variables are not set, prompt user for input
32+
if not username:
33+
username = input("Enter username: ")
34+
if not password:
35+
password = getpass("Enter password: ")
36+
37+
# Initialize Tapis object
38+
t = Tapis(base_url=base_url, username=username, password=password)
39+
40+
t.get_tokens()
41+
42+
return t

dapi/jobs/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
"""
2-
`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).
2+
`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).
33
44
55
## Features
66
7-
* Simplified TAPIS v2 Calls: No need to fiddle with complex API requests. `dapi` abstracts away the complexities.
7+
* Simplified TAPIS v3 Calls: No need to fiddle with complex API requests. `dapi` abstracts away the complexities.
88
99
* Seamless Integration with DesignSafe Jupyter Notebooks: Launch DesignSafe applications directly from the Jupyter environment.
1010
11-
## Installation
11+
# Installation
1212
1313
```shell
1414
pip3 install dapi
1515
```
1616
1717
"""
1818
from .dir import get_ds_path_uri
19-
from .jobs import get_status, runtime_summary, generate_job_info, get_archive_path
19+
from .jobs import get_status, runtime_summary, generate_job_info

dapi/jobs/dir.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import os
2+
from tapipy.tapis import Tapis
23

34

4-
def get_ds_path_uri(ag, path):
5+
def get_ds_path_uri(t: Tapis, path: str) -> str:
56
"""
6-
Given a path on DesignSafe, determine the correct input URI.
7+
Given a path on DesignSafe, determine the correct input URI for Tapis v3.
78
89
Args:
9-
ag (object): Agave object to fetch profiles or metadata.
10-
path (str): The directory path.
10+
t (Tapis): Tapis object to fetch profiles or metadata.
11+
path (str): The directory path.
1112
1213
Returns:
13-
str: The corresponding input URI.
14+
str: The corresponding input URI.
1415
1516
Raises:
16-
ValueError: If no matching directory pattern is found.
17+
ValueError: If no matching directory pattern is found.
1718
"""
18-
1919
# If any of the following directory patterns are found in the path,
2020
# process them accordingly.
2121
directory_patterns = [
@@ -28,9 +28,9 @@ def get_ds_path_uri(ag, path):
2828

2929
for pattern, storage, use_username in directory_patterns:
3030
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}"
31+
path = path.split(pattern, 1)[1].lstrip("/")
32+
input_dir = f"{t.username}/{path}" if use_username else path
33+
input_uri = f"tapis://{storage}/{input_dir}"
3434
return input_uri.replace(" ", "%20")
3535

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

4141
for pattern, prefix in project_patterns:
4242
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}"
43+
path = path.split(pattern, 1)[1].lstrip("/")
44+
project_id, *rest = path.split("/", 1)
45+
path = rest[0] if rest else ""
46+
47+
# Using Tapis v3 to get project UUID
48+
resp = t.get(f"https://designsafe-ci.org/api/projects/v2/{project_id}")
49+
project_uuid = resp.json()["baseProject"]["uuid"]
50+
51+
input_uri = f"tapis://{prefix}{project_uuid}/{path}"
4952
return input_uri.replace(" ", "%20")
5053

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

0 commit comments

Comments
 (0)