Skip to content

Commit b4f8783

Browse files
committed
Path to local from tapis uri
1 parent 48ee763 commit b4f8783

5 files changed

Lines changed: 381 additions & 150 deletions

File tree

dapi/client.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,24 @@ def translate_path_to_uri(self, *args, **kwargs) -> str:
183183
"""
184184
return files_module.get_ds_path_uri(self._tapis, *args, **kwargs)
185185

186+
def translate_uri_to_path(self, *args, **kwargs) -> str:
187+
"""Translate Tapis URIs to DesignSafe local paths.
188+
189+
This is a convenience wrapper around files_module.tapis_uri_to_local_path().
190+
191+
Args:
192+
*args: Positional arguments passed to tapis_uri_to_local_path().
193+
**kwargs: Keyword arguments passed to tapis_uri_to_local_path().
194+
195+
Returns:
196+
str: The corresponding DesignSafe local path (e.g., /home/jupyter/MyData/path).
197+
198+
Example:
199+
>>> local_path = client.files.translate_uri_to_path("tapis://designsafe.storage.default/user/data")
200+
>>> print(local_path) # "/home/jupyter/MyData/data"
201+
"""
202+
return files_module.tapis_uri_to_local_path(*args, **kwargs)
203+
186204
def upload(self, *args, **kwargs):
187205
"""Upload a local file to a Tapis storage system.
188206

dapi/files.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,77 @@ def _parse_tapis_uri(tapis_uri: str) -> (str, str):
4444
raise ValueError(f"Could not parse Tapis URI '{tapis_uri}': {e}") from e
4545

4646

47+
def tapis_uri_to_local_path(tapis_uri: str) -> str:
48+
"""Convert a Tapis URI to the corresponding DesignSafe local path.
49+
50+
Converts Tapis system URIs back to their equivalent DesignSafe local paths
51+
that would be accessible in a Jupyter environment. This is the reverse
52+
operation of get_ds_path_uri().
53+
54+
Args:
55+
tapis_uri (str): The Tapis URI to convert. Supported formats:
56+
- "tapis://designsafe.storage.default/username/path" -> "/home/jupyter/MyData/path"
57+
- "tapis://designsafe.storage.community/path" -> "/home/jupyter/CommunityData/path"
58+
- "tapis://project-*/path" -> "/home/jupyter/MyProjects/path"
59+
60+
Returns:
61+
str: The corresponding DesignSafe local path, or the original URI if
62+
it's not a recognized Tapis URI format.
63+
64+
Raises:
65+
ValueError: If the Tapis URI format is invalid.
66+
67+
Example:
68+
>>> local_path = tapis_uri_to_local_path("tapis://designsafe.storage.default/user/data/file.txt")
69+
>>> print(local_path) # "/home/jupyter/MyData/data/file.txt"
70+
71+
>>> local_path = tapis_uri_to_local_path("tapis://designsafe.storage.community/datasets/earthquake.csv")
72+
>>> print(local_path) # "/home/jupyter/CommunityData/datasets/earthquake.csv"
73+
"""
74+
if not tapis_uri.startswith("tapis://"):
75+
# Not a Tapis URI, return as-is
76+
return tapis_uri
77+
78+
try:
79+
# Parse the URI using the existing helper function
80+
system_id, path = _parse_tapis_uri(tapis_uri)
81+
82+
# Handle different system types
83+
if system_id == "designsafe.storage.default":
84+
# For MyData: tapis://designsafe.storage.default/username/path -> /home/jupyter/MyData/path
85+
# Remove the username (first path component)
86+
path_parts = path.split("/", 1) if path else [""]
87+
if len(path_parts) > 1:
88+
user_path = path_parts[1]
89+
return f"/home/jupyter/MyData/{user_path}"
90+
else:
91+
return "/home/jupyter/MyData/"
92+
93+
elif system_id == "designsafe.storage.community":
94+
# For CommunityData: tapis://designsafe.storage.community/path -> /home/jupyter/CommunityData/path
95+
return (
96+
f"/home/jupyter/CommunityData/{path}"
97+
if path
98+
else "/home/jupyter/CommunityData/"
99+
)
100+
101+
elif system_id.startswith("project-"):
102+
# For Projects: tapis://project-*/path -> /home/jupyter/MyProjects/path
103+
return (
104+
f"/home/jupyter/MyProjects/{path}"
105+
if path
106+
else "/home/jupyter/MyProjects/"
107+
)
108+
109+
else:
110+
# Unknown system type, return original URI
111+
return tapis_uri
112+
113+
except ValueError:
114+
# Invalid URI format, return original
115+
return tapis_uri
116+
117+
47118
def get_ds_path_uri(t: Tapis, path: str, verify_exists: bool = False) -> str:
48119
"""Translate DesignSafe-style paths to Tapis URIs.
49120

examples/opensees/OpenSeesMP-dapi.ipynb

Lines changed: 189 additions & 124 deletions
Large diffs are not rendered by default.

examples/opensees/OpenSeesMPJupyter-TAPISV3.ipynb

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,14 @@
9797
"# =================================\n",
9898
"\n",
9999
"from getpass import getpass\n",
100+
"\n",
100101
"# from tapipy.tapis import Tapis\n",
101102
"\n",
102103
"t = Tapis(\n",
103-
" base_url = \"https://designsafe.tapis.io\",\n",
104-
" username = getpass('input username: '),\n",
105-
" password = getpass('input password: '))\n",
104+
" base_url=\"https://designsafe.tapis.io\",\n",
105+
" username=getpass(\"input username: \"),\n",
106+
" password=getpass(\"input password: \"),\n",
107+
")\n",
106108
"t.get_tokens()"
107109
]
108110
},
@@ -116,11 +118,12 @@
116118
"import os\n",
117119
"import uuid\n",
118120
"from datetime import date\n",
119-
"from DS_GenFunctionsV3 import * #General python functions useful for DesignSafe\n",
121+
"from DS_GenFunctionsV3 import * # General python functions useful for DesignSafe\n",
122+
"\n",
120123
"# ---------------------------------------------------------------------------------\n",
121124
"# Identify folder with input file in DesignSafe\n",
122-
"cur_dir = os.getcwd()\n",
123-
"input_uri = DS_GetDir(cur_dir, t)"
125+
"cur_dir = os.getcwd()\n",
126+
"input_uri = DS_GetDir(cur_dir, t)"
124127
]
125128
},
126129
{
@@ -135,28 +138,28 @@
135138
"\n",
136139
"# ---------------------------------------------------------------------------------\n",
137140
"# Select tapis-app\n",
138-
"app_id = \"opensees-mp-s3\"\n",
139-
"app_version = \"3.7.0\"\n",
141+
"app_id = \"opensees-mp-s3\"\n",
142+
"app_version = \"3.7.0\"\n",
140143
"\n",
141144
"# ---------------------------------------------------------------------------------\n",
142145
"# Define file to run\n",
143146
"input_filename = \"Main_multiMotion.tcl\"\n",
144147
"\n",
145148
"# ---------------------------------------------------------------------------------\n",
146149
"# Define control tapis-app variables\n",
147-
"control_storage_id = \"designsafe.storage.default\"\n",
148-
"control_execSystem = \"stampede3\" #\"frontera\", \"stampede3-simcenter\"\n",
149-
"control_allocation = \"-A DesignSafe-SimCenter\" # MUST USE YOUR OWN ALLOCATION !!\n",
150-
"control_exec_Dir = \"DS_input\" # Folder with files including input_filename\n",
151-
"control_nodeCount = 1\n",
152-
"control_corespernode = 16\n",
153-
"control_memoryMB = 1000\n",
154-
"control_maxMinutes = 60\n",
150+
"control_storage_id = \"designsafe.storage.default\"\n",
151+
"control_execSystem = \"stampede3\" # \"frontera\", \"stampede3-simcenter\"\n",
152+
"control_allocation = \"-A DesignSafe-SimCenter\" # MUST USE YOUR OWN ALLOCATION !!\n",
153+
"control_exec_Dir = \"DS_input\" # Folder with files including input_filename\n",
154+
"control_nodeCount = 1\n",
155+
"control_corespernode = 16\n",
156+
"control_memoryMB = 1000\n",
157+
"control_maxMinutes = 60\n",
155158
"\n",
156159
"if control_execSystem == \"frontera\":\n",
157-
" control_batchQueue = \"development\" #\"normal\"\n",
158-
"elif control_execSystem == \"stampede3\" :\n",
159-
" control_batchQueue = \"skx-dev\" # \"skx\"\n",
160+
" control_batchQueue = \"development\" # \"normal\"\n",
161+
"elif control_execSystem == \"stampede3\":\n",
162+
" control_batchQueue = \"skx-dev\" # \"skx\"\n",
160163
"elif control_execSystem == \"stampede3-simcenter\":\n",
161164
" control_batchQueue = \"simcenter\"\n",
162165
"else:\n",
@@ -171,11 +174,13 @@
171174
"source": [
172175
"# -------------------------------------------------------------------------------\n",
173176
"# Define inputs\n",
174-
"fileInputs = [{\"name\": \"Input Directory\", \"sourceUrl\": f\"{input_uri}/{control_exec_Dir}\"}]\n",
177+
"fileInputs = [\n",
178+
" {\"name\": \"Input Directory\", \"sourceUrl\": f\"{input_uri}/{control_exec_Dir}\"}\n",
179+
"]\n",
175180
"\n",
176181
"# -------------------------------------------------------------------------------\n",
177182
"# Define parameters\n",
178-
"parameterSet = {\"appArgs\":[]}\n",
183+
"parameterSet = {\"appArgs\": []}\n",
179184
"parameterSet[\"appArgs\"].append({\"name\": \"Main Script\", \"arg\": input_filename})\n",
180185
"parameterSet[\"schedulerOptions\"] = [{\"arg\": control_allocation}]"
181186
]
@@ -199,7 +204,9 @@
199204
"job_description[\"execSystemExecDir\"] = \"${JobWorkingDir}\"\n",
200205
"\n",
201206
"job_description[\"archiveSystemId\"] = control_storage_id\n",
202-
"job_description[\"archiveSystemDir\"] = \"${EffectiveUserId}/tapis-jobs-archive/${JobCreateDate}/${JobUUID}\"\n",
207+
"job_description[\n",
208+
" \"archiveSystemDir\"\n",
209+
"] = \"${EffectiveUserId}/tapis-jobs-archive/${JobCreateDate}/${JobUUID}\"\n",
203210
"\n",
204211
"job_description[\"maxMinutes\"] = control_maxMinutes\n",
205212
"job_description[\"nodeCount\"] = control_nodeCount\n",
@@ -233,10 +240,12 @@
233240
"source": [
234241
"# Submit job\n",
235242
"job = t.jobs.submitJob(**job_description)\n",
236-
"jobUuid=job.uuid\n",
243+
"jobUuid = job.uuid\n",
237244
"\n",
238245
"print(\" Job launched. Status provided below\")\n",
239-
"print(\" Can also check in DedignSafe portal under - Workspace > Tools & Application > Job Status\")"
246+
"print(\n",
247+
" \" Can also check in DedignSafe portal under - Workspace > Tools & Application > Job Status\"\n",
248+
")"
240249
]
241250
},
242251
{
@@ -264,7 +273,7 @@
264273
}
265274
],
266275
"source": [
267-
"#Check status\n",
276+
"# Check status\n",
268277
"status = DS_GetStatus(t, jobUuid, tlapse=1)\n",
269278
"\n",
270279
"# Check Runtime\n",
@@ -323,7 +332,7 @@
323332
}
324333
],
325334
"source": [
326-
"os.chdir(jobinfo.archiveSystemDir.replace(user,'/home/jupyter/MyData'))\n",
335+
"os.chdir(jobinfo.archiveSystemDir.replace(user, \"/home/jupyter/MyData\"))\n",
327336
"# os.chdir(control_exec_Dir)\n",
328337
"os.chdir(\"inputDirectory\")"
329338
]
@@ -367,6 +376,7 @@
367376
"outputs": [],
368377
"source": [
369378
"from plotAcc import plot_acc\n",
379+
"\n",
370380
"plot_acc()"
371381
]
372382
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import unittest
2+
from dapi.files import tapis_uri_to_local_path
3+
4+
5+
class TestTapisUriToLocalPath(unittest.TestCase):
6+
"""Test cases for the tapis_uri_to_local_path function"""
7+
8+
def test_designsafe_storage_default_with_path(self):
9+
"""Test translation of designsafe.storage.default URIs with paths"""
10+
input_uri = "tapis://designsafe.storage.default/kks32/tapis-jobs-archive/2025-06-06Z/80986fb9-0d7e-440a-a4cf-ce54ec26226d-007"
11+
expected = "/home/jupyter/MyData/tapis-jobs-archive/2025-06-06Z/80986fb9-0d7e-440a-a4cf-ce54ec26226d-007"
12+
result = tapis_uri_to_local_path(input_uri)
13+
self.assertEqual(result, expected)
14+
15+
def test_designsafe_storage_default_simple_path(self):
16+
"""Test translation of simple designsafe.storage.default URI"""
17+
input_uri = "tapis://designsafe.storage.default/user/folder/file.txt"
18+
expected = "/home/jupyter/MyData/folder/file.txt"
19+
result = tapis_uri_to_local_path(input_uri)
20+
self.assertEqual(result, expected)
21+
22+
def test_designsafe_storage_default_root(self):
23+
"""Test translation of designsafe.storage.default root URI"""
24+
input_uri = "tapis://designsafe.storage.default/kks32/"
25+
expected = "/home/jupyter/MyData/"
26+
result = tapis_uri_to_local_path(input_uri)
27+
self.assertEqual(result, expected)
28+
29+
def test_designsafe_storage_community(self):
30+
"""Test translation of designsafe.storage.community URI"""
31+
input_uri = "tapis://designsafe.storage.community/datasets/earthquake.csv"
32+
expected = "/home/jupyter/CommunityData/datasets/earthquake.csv"
33+
result = tapis_uri_to_local_path(input_uri)
34+
self.assertEqual(result, expected)
35+
36+
def test_project_system(self):
37+
"""Test translation of project system URI"""
38+
input_uri = "tapis://project-1234-abcd/analysis/results.txt"
39+
expected = "/home/jupyter/MyProjects/analysis/results.txt"
40+
result = tapis_uri_to_local_path(input_uri)
41+
self.assertEqual(result, expected)
42+
43+
def test_unknown_system(self):
44+
"""Test that unknown systems return the original URI"""
45+
input_uri = "tapis://unknown-system/path/file.txt"
46+
expected = "tapis://unknown-system/path/file.txt"
47+
result = tapis_uri_to_local_path(input_uri)
48+
self.assertEqual(result, expected)
49+
50+
def test_non_tapis_uri(self):
51+
"""Test that non-Tapis URIs are returned unchanged"""
52+
input_uri = "/local/path/file.txt"
53+
expected = "/local/path/file.txt"
54+
result = tapis_uri_to_local_path(input_uri)
55+
self.assertEqual(result, expected)
56+
57+
def test_empty_path(self):
58+
"""Test handling of URIs with empty paths"""
59+
input_uri = "tapis://designsafe.storage.default/user"
60+
expected = "/home/jupyter/MyData/"
61+
result = tapis_uri_to_local_path(input_uri)
62+
self.assertEqual(result, expected)
63+
64+
65+
# This allows running the test from the command line
66+
if __name__ == "__main__":
67+
unittest.main()

0 commit comments

Comments
 (0)