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.
- {dapi-0.5.0 → dapi-0.5.2}/.github/workflows/docs.yml +3 -0
- {dapi-0.5.0 → dapi-0.5.2}/.gitignore +1 -0
- dapi-0.5.2/AUTHORS.md +5 -0
- dapi-0.5.2/CHANGELOG.md +74 -0
- {dapi-0.5.0 → dapi-0.5.2}/PKG-INFO +2 -2
- {dapi-0.5.0 → dapi-0.5.2}/README.md +1 -1
- {dapi-0.5.0 → dapi-0.5.2}/dapi/__init__.py +1 -1
- {dapi-0.5.0 → dapi-0.5.2}/dapi/client.py +221 -12
- {dapi-0.5.0 → dapi-0.5.2}/dapi/files.py +62 -71
- dapi-0.5.2/dapi/projects.py +261 -0
- dapi-0.5.2/dapi/publications.py +327 -0
- {dapi-0.5.0 → dapi-0.5.2}/dapi/systems.py +152 -68
- dapi-0.5.2/docs/authentication.md +110 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs/database.md +6 -63
- dapi-0.5.2/docs/examples/apps.md +47 -0
- dapi-0.5.2/docs/examples/mpm.md +162 -0
- dapi-0.5.2/docs/examples/openfoam.md +211 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs/examples/opensees.md +14 -178
- {dapi-0.5.0 → dapi-0.5.2}/docs/examples/pylauncher.md +1 -1
- dapi-0.5.2/docs/examples/pylauncher_opensees.md +77 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs/examples/tms_credentials.md +1 -1
- {dapi-0.5.0 → dapi-0.5.2}/docs/examples.md +48 -21
- {dapi-0.5.0 → dapi-0.5.2}/docs/files.md +11 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs/index.md +18 -17
- dapi-0.5.2/docs/installation.md +87 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs/jobs.md +42 -92
- dapi-0.5.2/docs/nheri.png +0 -0
- dapi-0.5.2/docs/projects.md +107 -0
- dapi-0.5.2/docs/publications.md +91 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs/quickstart.md +1 -1
- dapi-0.5.2/docs/systems.md +104 -0
- dapi-0.5.2/examples/files.ipynb +410 -0
- dapi-0.5.2/examples/projects.ipynb +1082 -0
- dapi-0.5.2/examples/publications.ipynb +691 -0
- dapi-0.5.2/examples/systems.ipynb +640 -0
- {dapi-0.5.0 → dapi-0.5.2}/myst.yml +10 -6
- {dapi-0.5.0 → dapi-0.5.2}/pyproject.toml +1 -1
- dapi-0.5.2/scripts/pre-commit +15 -0
- {dapi-0.5.0 → dapi-0.5.2}/tests/files/test_uri_translation.py +28 -0
- {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_dir_uri.py +35 -9
- dapi-0.5.2/tests/projects/__init__.py +0 -0
- dapi-0.5.2/tests/projects/test_projects.py +197 -0
- dapi-0.5.2/tests/publications/__init__.py +0 -0
- dapi-0.5.2/tests/publications/test_publications.py +185 -0
- dapi-0.5.0/AUTHORS.md +0 -4
- dapi-0.5.0/docs/authentication.md +0 -197
- dapi-0.5.0/docs/examples/apps.md +0 -226
- dapi-0.5.0/docs/examples/mpm.md +0 -339
- dapi-0.5.0/docs/examples/openfoam.md +0 -471
- dapi-0.5.0/docs/installation.md +0 -98
- dapi-0.5.0/docs/nheri.png +0 -0
- dapi-0.5.0/docs/systems.md +0 -42
- {dapi-0.5.0 → dapi-0.5.2}/.github/workflows/build-test.yml +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/.github/workflows/pypi.yml +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/DesignSafe-Badge.svg +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/LICENSE.md +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/_config.yml +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/dapi/apps.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/dapi/auth.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/dapi/db/__init__.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/dapi/db/accessor.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/dapi/db/config.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/dapi/db/db.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/dapi/exceptions.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/dapi/jobs.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/dapi/launcher.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/dapi.png +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs/apps.md +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs/examples/database.md +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs/favicon.ico +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs/stylesheets/extra.css +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs-api/apps.rst +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs-api/auth.rst +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs-api/client.rst +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs-api/conf.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs-api/database.rst +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs-api/exceptions.rst +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs-api/files.rst +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs-api/index.rst +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs-api/jobs.rst +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs-api/launcher.rst +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/docs-api/systems.rst +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/apps.ipynb +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/db.ipynb +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/mpm-minimal.ipynb +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/mpm.ipynb +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/uniaxial_stress/entity_sets.json +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/uniaxial_stress/mesh.txt +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/uniaxial_stress/mpm.json +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/uniaxial_stress/particles.txt +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/mpm/uniaxial_stress/results.ipynb +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/0/U +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/0/boundary +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/0/k +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/0/nut +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/0/omega +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/0/p +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/polyMesh/boundary +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/polyMesh/faces.gz +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/polyMesh/neighbour.gz +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/polyMesh/owner.gz +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/polyMesh/points.gz +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/transportProperties +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/constant/turbulenceProperties +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/foam.foam +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/system/blockMeshDict +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/system/controlDict +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/system/decomposeParDict +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/system/fvSchemes +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/DH1_run/system/fvSolution +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/frame.png +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/mesh.png +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/openfoam-minimal.ipynb +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/openfoam.ipynb +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/openfoam/paraview.png +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/Main_multiMotion.tcl +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/ProfileA.tcl +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/ProfileB.tcl +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/ProfileC.tcl +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/ProfileD.tcl +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/motion1.vel +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/motion2.vel +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/plotAcc.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/recordData.txt +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/DS_input/respSpectra.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/OpenSeesMP-dapi.ipynb +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/opensees/multi-freeField.png +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/pylauncher/pylauncher_opensees.ipynb +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/pylauncher/pylauncher_sweep.ipynb +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/examples/tms_credentials.ipynb +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/tests/auth/test_auth.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/tests/db/test_db.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/tests/files/test_encoding_consistency.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/__init__.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_archive_config.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_job_gen_jobinfo.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_job_status.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_list_jobs.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_parametric_sweep.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/tests/jobs/test_runtime_summary.py +0 -0
- {dapi-0.5.0 → dapi-0.5.2}/tests/systems/test_credentials.py +0 -0
dapi-0.5.2/AUTHORS.md
ADDED
dapi-0.5.2/CHANGELOG.md
ADDED
|
@@ -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.
|
|
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
|
|
@@ -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
|
|
272
|
-
"""List
|
|
462
|
+
def list(self, category: Optional[str] = None, output: str = "df"):
|
|
463
|
+
"""List Tapis systems you have access to.
|
|
273
464
|
|
|
274
|
-
|
|
465
|
+
Filters out internal and project-specific systems by default.
|
|
275
466
|
|
|
276
467
|
Args:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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[
|
|
474
|
+
DataFrame or List[Dict]: Systems with id, host, category, authn, credentials.
|
|
283
475
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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.
|
|
289
|
-
self._tapis,
|
|
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
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
284
|
+
"jupyter/MyProjects",
|
|
285
|
+
"jupyter/projects",
|
|
286
|
+
"/projects",
|
|
287
|
+
"projects",
|
|
288
|
+
"/MyProjects",
|
|
253
289
|
]
|
|
254
|
-
for pattern
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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"
|
|
308
|
+
f"Project system '{found_system_id}' not found via direct lookup."
|
|
314
309
|
)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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}'
|
|
331
|
-
break
|
|
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)
|