Skip to content

Commit e425bf6

Browse files
committed
Refactor abstraction for Dapi
1 parent e911ce8 commit e425bf6

File tree

21 files changed

+1193
-1021
lines changed

21 files changed

+1193
-1021
lines changed

dapi/__init__.py

Lines changed: 17 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,19 @@
11
"""
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).
3-
4-
## Features
5-
6-
### Jobs
7-
8-
* Get TAPIS v3 templates for jobs: No need to fiddle with complex API requests. `dapi` abstracts away the complexities.
9-
10-
* Seamless Integration with DesignSafe Jupyter Notebooks: Launch DesignSafe applications directly from the Jupyter environment.
11-
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-
32-
## Installation
33-
34-
```shell
35-
pip3 install dapi
36-
```
37-
2+
Dapi - A Python wrapper for interacting with DesignSafe resources via the Tapis API.
383
"""
39-
# from . import apps
40-
# from . import auth
41-
# from . import db
42-
# from . import jobs
43-
44-
from .core import DesignSafeAPI
45-
46-
__all__ = ["DesignSafeAPI", "apps", "auth", "db", "jobs", "core"]
4+
# Import the renamed client class
5+
from .client import DSClient
6+
from .exceptions import DapiException, FileOperationError, JobSubmissionError, JobMonitorError
7+
# Import JobDefinition here too for easier access like dapi.JobDefinition
8+
from .jobs import JobDefinition
9+
10+
__version__ = "1.1.0" # Example version
11+
12+
__all__ = [
13+
"DSClient", # Export the renamed class
14+
"JobDefinition", # Export JobDefinition
15+
"DapiException",
16+
"FileOperationError",
17+
"JobSubmissionError",
18+
"JobMonitorError",
19+
]

dapi/apps.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from tapipy.tapis import Tapis
2+
from tapipy.errors import BaseTapyException
3+
from typing import List, Any, Optional
4+
from .exceptions import AppDiscoveryError
5+
6+
def find_apps(
7+
t: Tapis, search_term: str, list_type: str = "ALL", verbose: bool = True
8+
) -> List[Tapis]:
9+
"""
10+
Search for Tapis apps matching a search term.
11+
12+
Args:
13+
t: Authenticated Tapis client instance.
14+
search_term: Name or partial name to search for. Use "" for all.
15+
list_type: One of 'OWNED', 'SHARED_PUBLIC', 'SHARED_DIRECT', 'READ_PERM', 'MINE', 'ALL'.
16+
verbose: If True, prints summary of found apps.
17+
18+
Returns:
19+
List of matching Tapis app objects.
20+
21+
Raises:
22+
AppDiscoveryError: If the search fails.
23+
"""
24+
try:
25+
# Use id.like for partial matching, ensure search term is handled
26+
search_query = f"(id.like.*{search_term}*)" if search_term else None
27+
results = t.apps.getApps(search=search_query, listType=list_type, select="id,version,owner") # Select fewer fields for speed
28+
29+
if verbose:
30+
if not results:
31+
print(f"No apps found matching '{search_term}' with listType '{list_type}'")
32+
else:
33+
print(f"\nFound {len(results)} matching apps:")
34+
for app in results:
35+
print(f"- {app.id} (Version: {app.version}, Owner: {app.owner})")
36+
print()
37+
return results
38+
except BaseTapyException as e:
39+
raise AppDiscoveryError(f"Failed to search for apps matching '{search_term}': {e}") from e
40+
except Exception as e:
41+
raise AppDiscoveryError(f"An unexpected error occurred while searching for apps: {e}") from e
42+
43+
44+
def get_app_details(t: Tapis, app_id: str, app_version: Optional[str] = None, verbose: bool = True) -> Optional[Tapis]:
45+
"""
46+
Get detailed information for a specific app ID and version (or latest).
47+
48+
Args:
49+
t: Authenticated Tapis client instance.
50+
app_id: Exact app ID to look up.
51+
app_version: Specific app version. If None, fetches the latest version.
52+
verbose: If True, prints basic app info.
53+
54+
Returns:
55+
Tapis app object with full details, or None if not found.
56+
57+
Raises:
58+
AppDiscoveryError: If fetching the app details fails.
59+
"""
60+
try:
61+
if app_version:
62+
app_info = t.apps.getApp(appId=app_id, appVersion=app_version)
63+
else:
64+
app_info = t.apps.getAppLatestVersion(appId=app_id)
65+
66+
if verbose:
67+
print(f"\nApp Details:")
68+
print(f" ID: {app_info.id}")
69+
print(f" Version: {app_info.version}")
70+
print(f" Owner: {app_info.owner}")
71+
if hasattr(app_info, 'jobAttributes') and hasattr(app_info.jobAttributes, 'execSystemId'):
72+
print(f" Execution System: {app_info.jobAttributes.execSystemId}")
73+
else:
74+
print(" Execution System: Not specified in jobAttributes")
75+
print(f" Description: {app_info.description}")
76+
return app_info
77+
except BaseTapyException as e:
78+
# Check for 404 specifically
79+
if hasattr(e, 'response') and e.response and e.response.status_code == 404:
80+
print(f"App '{app_id}' (Version: {app_version or 'latest'}) not found.")
81+
# Optionally, try searching for similar apps
82+
# print("\nAttempting to find similar apps:")
83+
# find_apps(t, app_id, verbose=True)
84+
return None
85+
else:
86+
print(f"Error getting app info for '{app_id}' (Version: {app_version or 'latest'}): {e}")
87+
raise AppDiscoveryError(f"Failed to get details for app '{app_id}': {e}") from e
88+
except Exception as e:
89+
print(f"An unexpected error occurred getting app info for '{app_id}': {e}")
90+
raise AppDiscoveryError(f"Unexpected error getting details for app '{app_id}': {e}") from e

dapi/apps/__init__.py

Lines changed: 0 additions & 3 deletions
This file was deleted.

dapi/apps/apps.py

Lines changed: 0 additions & 57 deletions
This file was deleted.

dapi/auth.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import os
2+
from getpass import getpass
3+
from tapipy.tapis import Tapis
4+
from tapipy.errors import BaseTapyException
5+
from dotenv import load_dotenv
6+
from .exceptions import AuthenticationError
7+
8+
def init(base_url: str = "https://designsafe.tapis.io",
9+
username: str = None,
10+
password: str = None,
11+
env_file: str = None) -> Tapis:
12+
"""
13+
Initialize and authenticate a Tapis client for DesignSafe.
14+
15+
Tries credentials in this order:
16+
1. Explicitly passed username/password arguments.
17+
2. Environment variables (DESIGNSAFE_USERNAME, DESIGNSAFE_PASSWORD).
18+
Loads from `env_file` (e.g., '.env') if specified, otherwise checks system env.
19+
3. Prompts user for username/password if none found.
20+
21+
Args:
22+
base_url: The Tapis base URL for DesignSafe.
23+
username: Explicit DesignSafe username.
24+
password: Explicit DesignSafe password.
25+
env_file: Path to a .env file to load credentials from.
26+
27+
Returns:
28+
An authenticated tapipy.Tapis object.
29+
30+
Raises:
31+
AuthenticationError: If authentication fails.
32+
"""
33+
# Load environment variables if a file path is provided
34+
if env_file:
35+
load_dotenv(dotenv_path=env_file)
36+
else:
37+
# Try loading from default .env if it exists, but don't require it
38+
load_dotenv()
39+
40+
# Determine credentials
41+
final_username = username or os.getenv("DESIGNSAFE_USERNAME")
42+
final_password = password or os.getenv("DESIGNSAFE_PASSWORD")
43+
44+
# Prompt if still missing
45+
if not final_username:
46+
final_username = input("Enter DesignSafe Username: ")
47+
if not final_password:
48+
# Use getpass for secure password entry in terminals
49+
try:
50+
final_password = getpass("Enter DesignSafe Password: ")
51+
except (EOFError, KeyboardInterrupt):
52+
raise AuthenticationError("Password input cancelled.")
53+
except Exception: # Fallback for non-terminal environments
54+
final_password = input("Enter DesignSafe Password: ")
55+
56+
57+
if not final_username or not final_password:
58+
raise AuthenticationError("Username and password are required.")
59+
60+
# Initialize Tapis object
61+
try:
62+
t = Tapis(base_url=base_url,
63+
username=final_username,
64+
password=final_password,
65+
download_latest_specs=False) # Avoid slow spec downloads by default
66+
67+
# Attempt to get tokens to verify credentials
68+
t.get_tokens()
69+
print("Authentication successful.")
70+
return t
71+
72+
except BaseTapyException as e:
73+
# Catch Tapis-specific errors during init or get_tokens
74+
raise AuthenticationError(f"Tapis authentication failed: {e}") from e
75+
except Exception as e:
76+
# Catch other potential errors
77+
raise AuthenticationError(f"An unexpected error occurred during authentication: {e}") from e

dapi/auth/__init__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

dapi/auth/auth.py

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)