dapi 0.5.0__tar.gz → 0.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. {dapi-0.5.0 → dapi-0.5.2}/.github/workflows/docs.yml +3 -0
  2. {dapi-0.5.0 → dapi-0.5.2}/.gitignore +1 -0
  3. dapi-0.5.2/AUTHORS.md +5 -0
  4. dapi-0.5.2/CHANGELOG.md +74 -0
  5. {dapi-0.5.0 → dapi-0.5.2}/PKG-INFO +2 -2
  6. {dapi-0.5.0 → dapi-0.5.2}/README.md +1 -1
  7. {dapi-0.5.0 → dapi-0.5.2}/dapi/__init__.py +1 -1
  8. {dapi-0.5.0 → dapi-0.5.2}/dapi/client.py +221 -12
  9. {dapi-0.5.0 → dapi-0.5.2}/dapi/files.py +62 -71
  10. dapi-0.5.2/dapi/projects.py +261 -0
  11. dapi-0.5.2/dapi/publications.py +327 -0
  12. {dapi-0.5.0 → dapi-0.5.2}/dapi/systems.py +152 -68
  13. dapi-0.5.2/docs/authentication.md +110 -0
  14. {dapi-0.5.0 → dapi-0.5.2}/docs/database.md +6 -63
  15. dapi-0.5.2/docs/examples/apps.md +47 -0
  16. dapi-0.5.2/docs/examples/mpm.md +162 -0
  17. dapi-0.5.2/docs/examples/openfoam.md +211 -0
  18. {dapi-0.5.0 → dapi-0.5.2}/docs/examples/opensees.md +14 -178
  19. {dapi-0.5.0 → dapi-0.5.2}/docs/examples/pylauncher.md +1 -1
  20. dapi-0.5.2/docs/examples/pylauncher_opensees.md +77 -0
  21. {dapi-0.5.0 → dapi-0.5.2}/docs/examples/tms_credentials.md +1 -1
  22. {dapi-0.5.0 → dapi-0.5.2}/docs/examples.md +48 -21
  23. {dapi-0.5.0 → dapi-0.5.2}/docs/files.md +11 -0
  24. {dapi-0.5.0 → dapi-0.5.2}/docs/index.md +18 -17
  25. dapi-0.5.2/docs/installation.md +87 -0
  26. {dapi-0.5.0 → dapi-0.5.2}/docs/jobs.md +42 -92
  27. dapi-0.5.2/docs/nheri.png +0 -0
  28. dapi-0.5.2/docs/projects.md +107 -0
  29. dapi-0.5.2/docs/publications.md +91 -0
  30. {dapi-0.5.0 → dapi-0.5.2}/docs/quickstart.md +1 -1
  31. dapi-0.5.2/docs/systems.md +104 -0
  32. dapi-0.5.2/examples/files.ipynb +410 -0
  33. dapi-0.5.2/examples/projects.ipynb +1082 -0
  34. dapi-0.5.2/examples/publications.ipynb +691 -0
  35. dapi-0.5.2/examples/systems.ipynb +640 -0
  36. {dapi-0.5.0 → dapi-0.5.2}/myst.yml +10 -6
  37. {dapi-0.5.0 → dapi-0.5.2}/pyproject.toml +1 -1
  38. dapi-0.5.2/scripts/pre-commit +15 -0
  39. {dapi-0.5.0 → dapi-0.5.2}/tests/files/test_uri_translation.py +28 -0
  40. {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_dir_uri.py +35 -9
  41. dapi-0.5.2/tests/projects/__init__.py +0 -0
  42. dapi-0.5.2/tests/projects/test_projects.py +197 -0
  43. dapi-0.5.2/tests/publications/__init__.py +0 -0
  44. dapi-0.5.2/tests/publications/test_publications.py +185 -0
  45. dapi-0.5.0/AUTHORS.md +0 -4
  46. dapi-0.5.0/docs/authentication.md +0 -197
  47. dapi-0.5.0/docs/examples/apps.md +0 -226
  48. dapi-0.5.0/docs/examples/mpm.md +0 -339
  49. dapi-0.5.0/docs/examples/openfoam.md +0 -471
  50. dapi-0.5.0/docs/installation.md +0 -98
  51. dapi-0.5.0/docs/nheri.png +0 -0
  52. dapi-0.5.0/docs/systems.md +0 -42
  53. {dapi-0.5.0 → dapi-0.5.2}/.github/workflows/build-test.yml +0 -0
  54. {dapi-0.5.0 → dapi-0.5.2}/.github/workflows/pypi.yml +0 -0
  55. {dapi-0.5.0 → dapi-0.5.2}/DesignSafe-Badge.svg +0 -0
  56. {dapi-0.5.0 → dapi-0.5.2}/LICENSE.md +0 -0
  57. {dapi-0.5.0 → dapi-0.5.2}/_config.yml +0 -0
  58. {dapi-0.5.0 → dapi-0.5.2}/dapi/apps.py +0 -0
  59. {dapi-0.5.0 → dapi-0.5.2}/dapi/auth.py +0 -0
  60. {dapi-0.5.0 → dapi-0.5.2}/dapi/db/__init__.py +0 -0
  61. {dapi-0.5.0 → dapi-0.5.2}/dapi/db/accessor.py +0 -0
  62. {dapi-0.5.0 → dapi-0.5.2}/dapi/db/config.py +0 -0
  63. {dapi-0.5.0 → dapi-0.5.2}/dapi/db/db.py +0 -0
  64. {dapi-0.5.0 → dapi-0.5.2}/dapi/exceptions.py +0 -0
  65. {dapi-0.5.0 → dapi-0.5.2}/dapi/jobs.py +0 -0
  66. {dapi-0.5.0 → dapi-0.5.2}/dapi/launcher.py +0 -0
  67. {dapi-0.5.0 → dapi-0.5.2}/dapi.png +0 -0
  68. {dapi-0.5.0 → dapi-0.5.2}/docs/apps.md +0 -0
  69. {dapi-0.5.0 → dapi-0.5.2}/docs/examples/database.md +0 -0
  70. {dapi-0.5.0 → dapi-0.5.2}/docs/favicon.ico +0 -0
  71. {dapi-0.5.0 → dapi-0.5.2}/docs/stylesheets/extra.css +0 -0
  72. {dapi-0.5.0 → dapi-0.5.2}/docs-api/apps.rst +0 -0
  73. {dapi-0.5.0 → dapi-0.5.2}/docs-api/auth.rst +0 -0
  74. {dapi-0.5.0 → dapi-0.5.2}/docs-api/client.rst +0 -0
  75. {dapi-0.5.0 → dapi-0.5.2}/docs-api/conf.py +0 -0
  76. {dapi-0.5.0 → dapi-0.5.2}/docs-api/database.rst +0 -0
  77. {dapi-0.5.0 → dapi-0.5.2}/docs-api/exceptions.rst +0 -0
  78. {dapi-0.5.0 → dapi-0.5.2}/docs-api/files.rst +0 -0
  79. {dapi-0.5.0 → dapi-0.5.2}/docs-api/index.rst +0 -0
  80. {dapi-0.5.0 → dapi-0.5.2}/docs-api/jobs.rst +0 -0
  81. {dapi-0.5.0 → dapi-0.5.2}/docs-api/launcher.rst +0 -0
  82. {dapi-0.5.0 → dapi-0.5.2}/docs-api/systems.rst +0 -0
  83. {dapi-0.5.0 → dapi-0.5.2}/examples/apps.ipynb +0 -0
  84. {dapi-0.5.0 → dapi-0.5.2}/examples/db.ipynb +0 -0
  85. {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/mpm-minimal.ipynb +0 -0
  86. {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/mpm.ipynb +0 -0
  87. {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/uniaxial_stress/entity_sets.json +0 -0
  88. {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/uniaxial_stress/mesh.txt +0 -0
  89. {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/uniaxial_stress/mpm.json +0 -0
  90. {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/uniaxial_stress/particles.txt +0 -0
  91. {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/uniaxial_stress/results.ipynb +0 -0
  92. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/0/U +0 -0
  93. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/0/boundary +0 -0
  94. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/0/k +0 -0
  95. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/0/nut +0 -0
  96. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/0/omega +0 -0
  97. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/0/p +0 -0
  98. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/polyMesh/boundary +0 -0
  99. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/polyMesh/faces.gz +0 -0
  100. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/polyMesh/neighbour.gz +0 -0
  101. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/polyMesh/owner.gz +0 -0
  102. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/polyMesh/points.gz +0 -0
  103. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/transportProperties +0 -0
  104. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/turbulenceProperties +0 -0
  105. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/foam.foam +0 -0
  106. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/system/blockMeshDict +0 -0
  107. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/system/controlDict +0 -0
  108. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/system/decomposeParDict +0 -0
  109. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/system/fvSchemes +0 -0
  110. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/system/fvSolution +0 -0
  111. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/frame.png +0 -0
  112. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/mesh.png +0 -0
  113. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/openfoam-minimal.ipynb +0 -0
  114. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/openfoam.ipynb +0 -0
  115. {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/paraview.png +0 -0
  116. {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/Main_multiMotion.tcl +0 -0
  117. {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/ProfileA.tcl +0 -0
  118. {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/ProfileB.tcl +0 -0
  119. {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/ProfileC.tcl +0 -0
  120. {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/ProfileD.tcl +0 -0
  121. {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/motion1.vel +0 -0
  122. {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/motion2.vel +0 -0
  123. {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/plotAcc.py +0 -0
  124. {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/recordData.txt +0 -0
  125. {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/respSpectra.py +0 -0
  126. {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/OpenSeesMP-dapi.ipynb +0 -0
  127. {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/multi-freeField.png +0 -0
  128. {dapi-0.5.0 → dapi-0.5.2}/examples/pylauncher/pylauncher_opensees.ipynb +0 -0
  129. {dapi-0.5.0 → dapi-0.5.2}/examples/pylauncher/pylauncher_sweep.ipynb +0 -0
  130. {dapi-0.5.0 → dapi-0.5.2}/examples/tms_credentials.ipynb +0 -0
  131. {dapi-0.5.0 → dapi-0.5.2}/tests/auth/test_auth.py +0 -0
  132. {dapi-0.5.0 → dapi-0.5.2}/tests/db/test_db.py +0 -0
  133. {dapi-0.5.0 → dapi-0.5.2}/tests/files/test_encoding_consistency.py +0 -0
  134. {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/__init__.py +0 -0
  135. {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_archive_config.py +0 -0
  136. {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_job_gen_jobinfo.py +0 -0
  137. {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_job_status.py +0 -0
  138. {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_list_jobs.py +0 -0
  139. {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_parametric_sweep.py +0 -0
  140. {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_runtime_summary.py +0 -0
  141. {dapi-0.5.0 → dapi-0.5.2}/tests/systems/test_credentials.py +0 -0
@@ -6,6 +6,9 @@ on:
6
6
  - main
7
7
  workflow_dispatch:
8
8
 
9
+ env:
10
+ BASE_URL: /${{ github.event.repository.name }}
11
+
9
12
  permissions:
10
13
  contents: read
11
14
  pages: write
@@ -180,3 +180,4 @@ cython_debug/
180
180
  *.sqlite
181
181
 
182
182
  .aider*
183
+ uv.lock
dapi-0.5.2/AUTHORS.md ADDED
@@ -0,0 +1,5 @@
1
+ # Authors
2
+
3
+ - Prof. Krishna Kumar, University of Texas at Austin
4
+ - Prof. Pedro Arduino, University of Washington
5
+ - Prof. Scott Brandenberg, University of California Los Angeles
@@ -0,0 +1,74 @@
1
+ # Changelog
2
+
3
+ ## v0.5.2
4
+
5
+ ### New features
6
+
7
+ - **Publications module** (`ds.publications`): search and access published datasets on DesignSafe
8
+ - `ds.publications.list()`: DataFrame of all published datasets (1,500+)
9
+ - `ds.publications.search()`: filter by `query`, `pi`, `keyword`, `publication_type` (AND logic)
10
+ - `ds.publications.get("PRJ-XXXX")`: full metadata with DOIs, description, keywords
11
+ - `ds.publications.files("PRJ-XXXX")`: list files in a published dataset
12
+ - **Systems listing** (`ds.systems.list()`): DataFrame of available HPC and storage systems
13
+ - Filter by category: `"hpc"`, `"storage"`, `"all"`
14
+ - Shows TMS credential status for HPC systems
15
+ - Filters out internal, duplicate, and project-specific systems
16
+ - **`ds.systems.queues()`** now returns a clean DataFrame instead of printing verbose output
17
+
18
+ ### Documentation
19
+
20
+ - New `docs/publications.md` with search filter reference
21
+ - Updated `docs/systems.md` with `list()` and DataFrame queues
22
+ - Added `examples/publications.ipynb` and `examples/systems.ipynb`
23
+ - Updated examples sidebar with all new notebooks
24
+
25
+ ## v0.5.1
26
+
27
+ ### New features
28
+
29
+ - **Projects module** (`ds.projects`): list, inspect, and access files in DesignSafe projects
30
+ - `ds.projects.list()`: returns a DataFrame of all projects you have access to
31
+ - `ds.projects.get("PRJ-XXXX")`: get full project metadata (title, PI, DOIs, keywords, team, systemId)
32
+ - `ds.projects.files("PRJ-XXXX")`: list files in a project as a DataFrame
33
+ - PRJ number to Tapis UUID resolution via DesignSafe portal API (`/api/projects/v2/`)
34
+ - **NHERI-Published and NEES storage support**: `ds.files.to_uri()` and `ds.files.to_path()` now handle `/NHERI-Published/` (`designsafe.storage.published`) and `/NEES/` (`nees.public`) paths
35
+ - **`ds.jobs.job(uuid)`**: get a `SubmittedJob` object for an existing job by UUID
36
+
37
+ ### Fixes
38
+
39
+ - Fix `ds.files.list()` failing on root paths (e.g., `tapis://designsafe.storage.community/`) where the parsed path was empty
40
+ - Fix project PRJ resolution: replaced broken Tapis system description search with DesignSafe portal API lookup
41
+
42
+ ### Documentation
43
+
44
+ - New `docs/projects.md` with full API reference and "How it works" section
45
+ - Updated `docs/files.md` with NHERI-Published and NEES path formats
46
+ - Added development section to `docs/installation.md` (dev branch install, editable install, pre-commit hook, running tests)
47
+
48
+ ### Developer experience
49
+
50
+ - Added `scripts/pre-commit` hook: auto-formats with `ruff format` and blocks commits failing `ruff check`
51
+ - Added `examples/files.ipynb` and `examples/projects.ipynb`
52
+
53
+ ## v0.5.0
54
+
55
+ ### New features
56
+
57
+ - **PyLauncher parameter sweeps** (`ds.jobs.parametric_sweep`): generate and submit parameter sweeps
58
+ - `ds.jobs.parametric_sweep.generate()`: generate `runsList.txt` and `call_pylauncher.py`, or preview as DataFrame
59
+ - `ds.jobs.parametric_sweep.submit()`: submit sweep jobs to TACC
60
+ - **`ds.jobs.list()`**: list jobs with optional filtering by app_id and status, returns DataFrame by default
61
+ - **Auto-TMS credentials**: `DSClient()` automatically sets up TMS credentials on TACC execution systems at init
62
+ - **Ruff**: switched from black to ruff for formatting and linting
63
+
64
+ ### API changes
65
+
66
+ - Renamed methods for brevity: `ds.jobs.generate()` (was `generate_job_info`), `ds.jobs.submit()` (was `submit_job`)
67
+ - `ds.jobs.list()` supports `output="df"` (default), `"list"`, or `"raw"`
68
+ - Added `ds.files.to_uri()` and `ds.files.to_path()` for path translation
69
+
70
+ ### Infrastructure
71
+
72
+ - Migrated from Poetry to uv + hatchling
73
+ - Migrated docs from mkdocs to Jupyter Book v2 (MyST)
74
+ - Added TMS credential management (`ds.systems.establish_credentials()`, `check_credentials()`, `revoke_credentials()`)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dapi
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: DesignSafe API
5
5
  Author-email: Krishna Kumar <krishnak@utexas.edu>, Pedro Arduino <parduino@uw.edu>, Scott Brandenberg <sjbrandenberg@ucla.edu>
6
6
  License-Expression: MIT
@@ -188,6 +188,6 @@ jupyter-book start
188
188
 
189
189
  ## Authors
190
190
 
191
- - Krishna Kumar, University of Texas at Austin
191
+ - Prof. Krishna Kumar, University of Texas at Austin
192
192
  - Prof. Pedro Arduino, University of Washington
193
193
  - Prof. Scott Brandenberg, University of California Los Angeles
@@ -157,6 +157,6 @@ jupyter-book start
157
157
 
158
158
  ## Authors
159
159
 
160
- - Krishna Kumar, University of Texas at Austin
160
+ - Prof. Krishna Kumar, University of Texas at Austin
161
161
  - Prof. Pedro Arduino, University of Washington
162
162
  - Prof. Scott Brandenberg, University of California Los Angeles
@@ -49,7 +49,7 @@ from .jobs import (
49
49
  TAPIS_TERMINAL_STATES,
50
50
  )
51
51
 
52
- __version__ = "0.5.0"
52
+ __version__ = "0.5.2"
53
53
 
54
54
  __all__ = [
55
55
  "DSClient",
@@ -6,6 +6,8 @@ from . import files as files_module
6
6
  from . import jobs as jobs_module
7
7
  from . import systems as systems_module
8
8
  from . import launcher as launcher_module
9
+ from . import projects as projects_module
10
+ from . import publications as publications_module
9
11
  from .db.accessor import DatabaseAccessor
10
12
 
11
13
  # Import only the necessary classes/functions from jobs
@@ -31,6 +33,8 @@ class DSClient:
31
33
  apps (AppMethods): Interface for application discovery and details.
32
34
  files (FileMethods): Interface for file operations (upload, download, list).
33
35
  jobs (JobMethods): Interface for job submission and monitoring.
36
+ projects (ProjectMethods): Interface for DesignSafe project management.
37
+ publications (PublicationMethods): Interface for published datasets.
34
38
  systems (SystemMethods): Interface for system information and queues.
35
39
  db (DatabaseAccessor): Interface for database connections and queries.
36
40
 
@@ -90,6 +94,8 @@ class DSClient:
90
94
  self.apps = AppMethods(self.tapis)
91
95
  self.files = FileMethods(self.tapis)
92
96
  self.jobs = JobMethods(self.tapis)
97
+ self.projects = ProjectMethods(self.tapis)
98
+ self.publications = PublicationMethods(self.tapis)
93
99
  self.systems = SystemMethods(self.tapis)
94
100
  self.db = DatabaseAccessor()
95
101
 
@@ -250,6 +256,191 @@ class FileMethods:
250
256
  return files_module.list_files(self._tapis, *args, **kwargs)
251
257
 
252
258
 
259
+ class ProjectMethods:
260
+ """Interface for DesignSafe project management.
261
+
262
+ Provides methods for listing projects, getting project metadata,
263
+ and listing files within projects.
264
+
265
+ Args:
266
+ tapis_client (Tapis): Authenticated Tapis client instance.
267
+ """
268
+
269
+ def __init__(self, tapis_client: Tapis):
270
+ self._tapis = tapis_client
271
+
272
+ def list(self, limit: int = 100, offset: int = 0, output: str = "df"):
273
+ """List DesignSafe projects you have access to.
274
+
275
+ Args:
276
+ limit (int, optional): Maximum projects to return. Defaults to 100.
277
+ offset (int, optional): Number of projects to skip. Defaults to 0.
278
+ output (str, optional): "df" for DataFrame (default), "list" for
279
+ list of dicts.
280
+
281
+ Returns:
282
+ DataFrame or List[Dict]: Projects with projectId, title, pi, type, etc.
283
+
284
+ Example:
285
+ >>> ds.projects.list() # returns a DataFrame
286
+ >>> ds.projects.list(output="list") # returns list of dicts
287
+ """
288
+ return projects_module.list_projects(
289
+ self._tapis, limit=limit, offset=offset, output=output
290
+ )
291
+
292
+ def get(self, project_id: str) -> Dict:
293
+ """Get detailed metadata for a project.
294
+
295
+ Args:
296
+ project_id (str): Project ID (e.g., "PRJ-1305").
297
+
298
+ Returns:
299
+ Dict: Project metadata including title, description, PI, team,
300
+ DOIs, award numbers, keywords, systemId.
301
+
302
+ Example:
303
+ >>> info = ds.projects.get("PRJ-6270")
304
+ >>> print(info["title"])
305
+ >>> print(info["systemId"])
306
+ """
307
+ return projects_module.get_project(self._tapis, project_id)
308
+
309
+ def files(
310
+ self, project_id: str, path: str = "/", limit: int = 100, output: str = "df"
311
+ ):
312
+ """List files in a project.
313
+
314
+ Args:
315
+ project_id (str): Project ID (e.g., "PRJ-1305").
316
+ path (str, optional): Path within the project. Defaults to "/".
317
+ limit (int, optional): Max items to return. Defaults to 100.
318
+ output (str, optional): "df" for DataFrame (default), "raw" for
319
+ Tapis file objects.
320
+
321
+ Returns:
322
+ DataFrame or List: Files with name, type, size, lastModified, path.
323
+
324
+ Example:
325
+ >>> ds.projects.files("PRJ-1305", "/Training/")
326
+ >>> ds.projects.files("PRJ-1305", output="raw")
327
+ """
328
+ return projects_module.list_project_files(
329
+ self._tapis, project_id, path=path, limit=limit, output=output
330
+ )
331
+
332
+
333
+ class PublicationMethods:
334
+ """Interface for DesignSafe published datasets.
335
+
336
+ Provides methods for listing, searching, and inspecting published datasets
337
+ and their files on DesignSafe.
338
+
339
+ Args:
340
+ tapis_client (Tapis): Authenticated Tapis client instance.
341
+ """
342
+
343
+ def __init__(self, tapis_client: Tapis):
344
+ self._tapis = tapis_client
345
+
346
+ def list(self, limit: int = 100, offset: int = 0, output: str = "df"):
347
+ """List published datasets on DesignSafe.
348
+
349
+ Args:
350
+ limit (int, optional): Maximum publications to return. Defaults to 100.
351
+ offset (int, optional): Number to skip. Defaults to 0.
352
+ output (str, optional): "df" for DataFrame (default), "list" for dicts.
353
+
354
+ Returns:
355
+ DataFrame or List[Dict]: Publications with projectId, title, pi, type, keywords, created.
356
+
357
+ Example:
358
+ >>> ds.publications.list()
359
+ """
360
+ return publications_module.list_publications(
361
+ self._tapis, limit=limit, offset=offset, output=output
362
+ )
363
+
364
+ def search(
365
+ self,
366
+ query: Optional[str] = None,
367
+ *,
368
+ pi: Optional[str] = None,
369
+ keyword: Optional[str] = None,
370
+ publication_type: Optional[str] = None,
371
+ limit: int = 100,
372
+ output: str = "df",
373
+ ):
374
+ """Search published datasets with optional filters.
375
+
376
+ All filters are AND-combined and case-insensitive.
377
+
378
+ Args:
379
+ query (str, optional): General search across title, description, keywords, PI.
380
+ pi (str, optional): Filter by PI name.
381
+ keyword (str, optional): Filter by keyword.
382
+ publication_type (str, optional): Filter by type: "simulation",
383
+ "experimental", "field_recon", "other", "hybrid_simulation".
384
+ limit (int, optional): Max publications to fetch. Defaults to 100.
385
+ output (str, optional): "df" for DataFrame (default), "list" for dicts.
386
+
387
+ Returns:
388
+ DataFrame or List[Dict]: Matching publications.
389
+
390
+ Example:
391
+ >>> ds.publications.search("liquefaction")
392
+ >>> ds.publications.search(pi="Rathje")
393
+ >>> ds.publications.search(
394
+ ... keyword="storm surge", publication_type="simulation"
395
+ ... )
396
+ """
397
+ return publications_module.search_publications(
398
+ self._tapis,
399
+ query,
400
+ pi=pi,
401
+ keyword=keyword,
402
+ publication_type=publication_type,
403
+ limit=limit,
404
+ output=output,
405
+ )
406
+
407
+ def get(self, project_id: str) -> Dict:
408
+ """Get detailed metadata for a published dataset.
409
+
410
+ Args:
411
+ project_id (str): Project ID (e.g., "PRJ-1271").
412
+
413
+ Returns:
414
+ Dict: Publication metadata including title, description, DOIs, keywords, PI.
415
+
416
+ Example:
417
+ >>> info = ds.publications.get("PRJ-6270")
418
+ >>> print(info["dois"])
419
+ """
420
+ return publications_module.get_publication(self._tapis, project_id)
421
+
422
+ def files(
423
+ self, project_id: str, path: str = "/", limit: int = 100, output: str = "df"
424
+ ):
425
+ """List files in a published dataset.
426
+
427
+ Args:
428
+ project_id (str): Project ID (e.g., "PRJ-1271").
429
+ path (str, optional): Path within the publication. Defaults to "/".
430
+ limit (int, optional): Max items to return. Defaults to 100.
431
+ output (str, optional): "df" for DataFrame (default), "raw" for Tapis objects.
432
+
433
+ Returns:
434
+ DataFrame or List: Files with name, type, size, lastModified, path.
435
+
436
+ Example:
437
+ >>> ds.publications.files("PRJ-1271")
438
+ """
439
+ return publications_module.list_publication_files(
440
+ self._tapis, project_id, path=path, limit=limit, output=output
441
+ )
442
+
443
+
253
444
  class SystemMethods:
254
445
  """Interface for Tapis system information and queue management.
255
446
 
@@ -268,27 +459,45 @@ class SystemMethods:
268
459
  """
269
460
  self._tapis = tapis_client
270
461
 
271
- def queues(self, system_id: str, verbose: bool = True) -> List[Any]:
272
- """List logical queues available on a Tapis execution system.
462
+ def list(self, category: Optional[str] = None, output: str = "df"):
463
+ """List Tapis systems you have access to.
273
464
 
274
- This is a convenience wrapper around systems_module.list_system_queues().
465
+ Filters out internal and project-specific systems by default.
275
466
 
276
467
  Args:
277
- system_id (str): The ID of the execution system (e.g., 'frontera').
278
- verbose (bool, optional): If True, prints detailed queue information.
279
- Defaults to True.
468
+ category (str, optional): "hpc" for execution systems,
469
+ "storage" for storage systems, "all" for everything,
470
+ None for HPC + storage (default).
471
+ output (str, optional): "df" for DataFrame (default), "list" for dicts.
280
472
 
281
473
  Returns:
282
- List[Any]: List of queue objects with queue configuration details.
474
+ DataFrame or List[Dict]: Systems with id, host, category, authn, credentials.
283
475
 
284
- Raises:
285
- SystemInfoError: If the system is not found or queue retrieval fails.
286
- ValueError: If system_id is empty.
476
+ Example:
477
+ >>> ds.systems.list() # HPC + storage
478
+ >>> ds.systems.list("hpc") # HPC only with credential status
479
+ >>> ds.systems.list("storage") # Storage only
480
+ >>> ds.systems.list("all") # Everything including internal
287
481
  """
288
- return systems_module.list_system_queues(
289
- self._tapis, system_id, verbose=verbose
482
+ return systems_module.list_systems(
483
+ self._tapis, category=category, output=output
290
484
  )
291
485
 
486
+ def queues(self, system_id: str, output: str = "df"):
487
+ """List batch queues available on a Tapis execution system.
488
+
489
+ Args:
490
+ system_id (str): The ID of the execution system (e.g., "stampede3").
491
+ output (str, optional): "df" for DataFrame (default), "raw" for Tapis objects.
492
+
493
+ Returns:
494
+ DataFrame or List: Queues with name, maxNodes, maxMinutes, etc.
495
+
496
+ Example:
497
+ >>> ds.systems.queues("stampede3")
498
+ """
499
+ return systems_module.list_system_queues(self._tapis, system_id, output=output)
500
+
292
501
  def check_credentials(self, system_id: str, username: str = None) -> bool:
293
502
  """Check whether TMS credentials exist for a user on a system.
294
503
 
@@ -2,8 +2,6 @@
2
2
  import os
3
3
  import urllib.parse
4
4
 
5
- # No JWT needed if we rely on t.username
6
- # import jwt
7
5
  from tapipy.tapis import Tapis
8
6
  from tapipy.errors import BaseTapyException
9
7
  from .exceptions import FileOperationError, AuthenticationError
@@ -131,6 +129,16 @@ def tapis_uri_to_local_path(tapis_uri: str) -> str:
131
129
  else "/home/jupyter/CommunityData/"
132
130
  )
133
131
 
132
+ elif system_id == "designsafe.storage.published":
133
+ return (
134
+ f"/home/jupyter/NHERI-Published/{path}"
135
+ if path
136
+ else "/home/jupyter/NHERI-Published/"
137
+ )
138
+
139
+ elif system_id == "nees.public":
140
+ return f"/home/jupyter/NEES/{path}" if path else "/home/jupyter/NEES/"
141
+
134
142
  elif system_id.startswith("project-"):
135
143
  # For Projects: tapis://project-*/path -> /home/jupyter/MyProjects/path
136
144
  return (
@@ -242,16 +250,44 @@ def get_ds_path_uri(t: Tapis, path: str, verify_exists: bool = False) -> str:
242
250
  print(f"Translated '{path}' to '{input_uri}'")
243
251
  break # Found match, exit loop
244
252
 
245
- # 3. Handle Project variations (if not already matched)
253
+ # 3. Handle NHERI-Published variations (if not already matched)
254
+ if input_uri is None:
255
+ published_patterns = [
256
+ ("jupyter/NHERI-Published", "designsafe.storage.published", False),
257
+ ("/NHERI-Published", "designsafe.storage.published", False),
258
+ ("NHERI-Published", "designsafe.storage.published", False),
259
+ ]
260
+ for pattern, storage_system_id, use_username in published_patterns:
261
+ if pattern in path:
262
+ path_remainder = path.split(pattern, 1)[1].lstrip("/")
263
+ input_uri = f"tapis://{storage_system_id}/{path_remainder}"
264
+ print(f"Translated '{path}' to '{input_uri}'")
265
+ break
266
+
267
+ # 4. Handle NEES variations (if not already matched)
268
+ if input_uri is None:
269
+ nees_patterns = [
270
+ ("jupyter/NEES", "nees.public", False),
271
+ ("/NEES", "nees.public", False),
272
+ ("NEES", "nees.public", False),
273
+ ]
274
+ for pattern, storage_system_id, use_username in nees_patterns:
275
+ if pattern in path:
276
+ path_remainder = path.split(pattern, 1)[1].lstrip("/")
277
+ input_uri = f"tapis://{storage_system_id}/{path_remainder}"
278
+ print(f"Translated '{path}' to '{input_uri}'")
279
+ break
280
+
281
+ # 5. Handle Project variations (if not already matched)
246
282
  if input_uri is None:
247
283
  project_patterns = [
248
- ("jupyter/MyProjects", "project-"),
249
- ("jupyter/projects", "project-"),
250
- ("/projects", "project-"),
251
- ("projects", "project-"),
252
- ("/MyProjects", "project-"),
284
+ "jupyter/MyProjects",
285
+ "jupyter/projects",
286
+ "/projects",
287
+ "projects",
288
+ "/MyProjects",
253
289
  ]
254
- for pattern, system_prefix in project_patterns:
290
+ for pattern in project_patterns:
255
291
  if pattern in path:
256
292
  path_remainder_full = path.split(pattern, 1)[1].lstrip("/")
257
293
  if not path_remainder_full:
@@ -262,73 +298,26 @@ def get_ds_path_uri(t: Tapis, path: str, verify_exists: bool = False) -> str:
262
298
  project_id_part = parts[0]
263
299
  path_within_project = parts[1] if len(parts) > 1 else ""
264
300
 
265
- print(f"Searching Tapis systems for project ID '{project_id_part}'...")
266
- found_system_id = None
267
- try:
268
- search_query = (
269
- f"description.like.%{project_id_part}%&id.like.{system_prefix}*"
270
- )
271
- systems = t.systems.getSystems(
272
- search=search_query,
273
- listType="ALL",
274
- select="id,owner,description",
275
- limit=10,
276
- )
277
- matches = []
278
- if systems:
279
- for sys in systems:
280
- if (
281
- project_id_part.lower()
282
- in getattr(sys, "description", "").lower()
283
- ):
284
- matches.append(sys.id)
285
- if len(matches) == 1:
286
- found_system_id = matches[0]
287
- print(f"Found unique matching system: {found_system_id}")
288
- elif len(matches) == 0:
289
- if "-" in project_id_part and len(project_id_part) > 30:
290
- potential_sys_id = f"{system_prefix}{project_id_part}"
291
- print(
292
- f"Search failed, attempting direct lookup for system ID: {potential_sys_id}"
293
- )
294
- try:
295
- t.systems.getSystem(
296
- systemId=potential_sys_id, select="id"
297
- ) # Select minimal field
298
- found_system_id = potential_sys_id
299
- print(f"Direct lookup successful: {found_system_id}")
300
- except BaseTapyException:
301
- print(
302
- f"Direct lookup for {potential_sys_id} also failed."
303
- )
304
- raise FileOperationError(
305
- f"No project system found matching ID '{project_id_part}' via Tapis v3 search or direct UUID lookup."
306
- )
307
- else:
308
- raise FileOperationError(
309
- f"No project system found matching ID '{project_id_part}' via Tapis v3 search."
310
- )
311
- else:
301
+ # If it looks like a UUID already, use it directly
302
+ if "-" in project_id_part and len(project_id_part) > 30:
303
+ found_system_id = f"project-{project_id_part}"
304
+ try:
305
+ t.systems.getSystem(systemId=found_system_id, select="id")
306
+ except BaseTapyException:
312
307
  raise FileOperationError(
313
- f"Multiple project systems found potentially matching ID '{project_id_part}': {matches}. Cannot determine unique system."
308
+ f"Project system '{found_system_id}' not found via direct lookup."
314
309
  )
315
- except BaseTapyException as e:
316
- raise FileOperationError(
317
- f"Tapis API error searching for project system '{project_id_part}': {e}"
318
- ) from e
319
- except Exception as e:
320
- raise FileOperationError(
321
- f"Unexpected error searching for project system '{project_id_part}': {e}"
322
- ) from e
323
-
324
- if not found_system_id:
325
- raise FileOperationError(
326
- f"Could not resolve project ID '{project_id_part}' to a Tapis system ID."
310
+ else:
311
+ # Resolve PRJ number via DesignSafe projects API
312
+ from . import projects as projects_module
313
+
314
+ found_system_id = projects_module.resolve_project_uuid(
315
+ t, project_id_part
327
316
  )
328
317
 
329
318
  input_uri = f"tapis://{found_system_id}/{path_within_project}"
330
- print(f"Translated '{path}' to '{input_uri}' using Tapis v3 lookup")
331
- break # Found match, exit loop
319
+ print(f"Translated '{path}' to '{input_uri}'")
320
+ break
332
321
 
333
322
  # 4. Handle direct tapis:// URI input (if not already matched)
334
323
  if input_uri is None and path.startswith("tapis://"):
@@ -514,6 +503,8 @@ def list_files(
514
503
  """
515
504
  try:
516
505
  system_id, path = _parse_tapis_uri(remote_uri)
506
+ if not path:
507
+ path = "/"
517
508
  print(f"Listing files in system '{system_id}' at path '{path}'...")
518
509
  # URL-encode the path for API call
519
510
  encoded_path = _safe_quote(path)