dapi 0.4.5__tar.gz → 0.4.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dapi
3
- Version: 0.4.5
3
+ Version: 0.4.7
4
4
  Summary: DesignSafe API
5
5
  Author: Krishna Kumar
6
6
  Author-email: krishnak@utexas.edu
@@ -18,6 +18,7 @@ Requires-Dist: mkdocs (>=1.6.1,<2.0.0)
18
18
  Requires-Dist: mkdocs-autorefs (>=1.4.2,<2.0.0)
19
19
  Requires-Dist: mkdocs-material (>=9.6.14,<10.0.0)
20
20
  Requires-Dist: mkdocstrings (>=0.29.1,<0.30.0)
21
+ Requires-Dist: mkdocstrings-python (>=1.16.12,<2.0.0)
21
22
  Requires-Dist: numpy (>=1.21.0) ; python_version >= "3.10"
22
23
  Requires-Dist: numpy (>=1.21.0,<2.0) ; python_version == "3.9"
23
24
  Requires-Dist: pandas (>=1.3.0,<3.0.0) ; python_version == "3.9"
@@ -180,10 +181,3 @@ poetry run mkdocs serve
180
181
  ```
181
182
 
182
183
  This will start a local server at `http://127.0.0.1:8000/dapi/` where you can view the documentation.
183
-
184
- ### API docs
185
- To generate API docs:
186
-
187
- ```
188
- pdoc --html --output-dir api-docs dapi --force
189
- ```
@@ -148,11 +148,4 @@ poetry install
148
148
  poetry run mkdocs serve
149
149
  ```
150
150
 
151
- This will start a local server at `http://127.0.0.1:8000/dapi/` where you can view the documentation.
152
-
153
- ### API docs
154
- To generate API docs:
155
-
156
- ```
157
- pdoc --html --output-dir api-docs dapi --force
158
- ```
151
+ This will start a local server at `http://127.0.0.1:8000/dapi/` where you can view the documentation.
@@ -11,6 +11,36 @@ from .exceptions import FileOperationError, AuthenticationError
11
11
  from typing import List
12
12
 
13
13
 
14
+ def _safe_quote(path: str) -> str:
15
+ """Safely URL-encode a path, avoiding double encoding.
16
+
17
+ Args:
18
+ path (str): The path to encode
19
+
20
+ Returns:
21
+ str: URL-encoded path
22
+
23
+ Example:
24
+ >>> _safe_quote("folder with spaces")
25
+ 'folder%20with%20spaces'
26
+ >>> _safe_quote("folder%20with%20spaces") # Already encoded
27
+ 'folder%20with%20spaces'
28
+ """
29
+ # Check if the path appears to be already URL-encoded
30
+ # by trying to decode it and seeing if it changes
31
+ try:
32
+ decoded = urllib.parse.unquote(path)
33
+ if decoded != path:
34
+ # Path was URL-encoded, return as-is to avoid double encoding
35
+ return path
36
+ else:
37
+ # Path was not URL-encoded, encode it
38
+ return urllib.parse.quote(path)
39
+ except Exception:
40
+ # If there's any error in decoding, just encode the original path
41
+ return urllib.parse.quote(path)
42
+
43
+
14
44
  # _parse_tapis_uri helper remains the same
15
45
  def _parse_tapis_uri(tapis_uri: str) -> (str, str):
16
46
  """Parse a Tapis URI into system ID and path components.
@@ -19,7 +49,7 @@ def _parse_tapis_uri(tapis_uri: str) -> (str, str):
19
49
  tapis_uri (str): URI in the format 'tapis://system_id/path'.
20
50
 
21
51
  Returns:
22
- tuple: A tuple containing (system_id, path) where path is URL-decoded.
52
+ tuple: A tuple containing (system_id, path).
23
53
 
24
54
  Raises:
25
55
  ValueError: If the URI format is invalid or missing required components.
@@ -190,8 +220,7 @@ def get_ds_path_uri(t: Tapis, path: str, verify_exists: bool = False) -> str:
190
220
  )
191
221
  else:
192
222
  tapis_path = path_remainder
193
- encoded_path = urllib.parse.quote(tapis_path)
194
- input_uri = f"tapis://{storage_system_id}/{encoded_path}"
223
+ input_uri = f"tapis://{storage_system_id}/{tapis_path}"
195
224
  print(f"Translated '{path}' to '{input_uri}' using t.username")
196
225
  break # Found match, exit loop
197
226
 
@@ -206,8 +235,7 @@ def get_ds_path_uri(t: Tapis, path: str, verify_exists: bool = False) -> str:
206
235
  if pattern in path:
207
236
  path_remainder = path.split(pattern, 1)[1].lstrip("/")
208
237
  tapis_path = path_remainder
209
- encoded_path = urllib.parse.quote(tapis_path)
210
- input_uri = f"tapis://{storage_system_id}/{encoded_path}"
238
+ input_uri = f"tapis://{storage_system_id}/{tapis_path}"
211
239
  print(f"Translated '{path}' to '{input_uri}'")
212
240
  break # Found match, exit loop
213
241
 
@@ -295,8 +323,7 @@ def get_ds_path_uri(t: Tapis, path: str, verify_exists: bool = False) -> str:
295
323
  f"Could not resolve project ID '{project_id_part}' to a Tapis system ID."
296
324
  )
297
325
 
298
- encoded_path_within_project = urllib.parse.quote(path_within_project)
299
- input_uri = f"tapis://{found_system_id}/{encoded_path_within_project}"
326
+ input_uri = f"tapis://{found_system_id}/{path_within_project}"
300
327
  print(f"Translated '{path}' to '{input_uri}' using Tapis v3 lookup")
301
328
  break # Found match, exit loop
302
329
 
@@ -316,26 +343,26 @@ def get_ds_path_uri(t: Tapis, path: str, verify_exists: bool = False) -> str:
316
343
  print(f"Verifying existence of translated path: {input_uri}")
317
344
  try:
318
345
  system_id, remote_path = _parse_tapis_uri(input_uri)
319
- # Decode the path part for the listFiles call, as it expects unencoded paths
320
- decoded_remote_path = urllib.parse.unquote(remote_path)
321
- print(f"Checking system '{system_id}' for path '{decoded_remote_path}'...")
346
+ # The Tapis API expects URL-encoded paths when they contain spaces or special characters
347
+ encoded_remote_path = _safe_quote(remote_path)
348
+ print(f"Checking system '{system_id}' for path '{remote_path}'...")
322
349
  # Use limit=1 for efficiency, we only care if it *exists*
323
350
  # Note: listFiles might return successfully for the *parent* directory
324
351
  # if the final component doesn't exist. A more robust check might
325
352
  # involve checking the result count or specific item name, but this
326
353
  # basic check catches non-existent parent directories.
327
- t.files.listFiles(systemId=system_id, path=decoded_remote_path, limit=1)
354
+ t.files.listFiles(systemId=system_id, path=encoded_remote_path, limit=1)
328
355
  print(f"Verification successful: Path exists.")
329
356
  except BaseTapyException as e:
330
357
  # Specifically check for 404 on the listFiles call
331
358
  if hasattr(e, "response") and e.response and e.response.status_code == 404:
332
359
  raise FileOperationError(
333
- f"Verification failed: Path '{decoded_remote_path}' does not exist on system '{system_id}'. Translated URI: {input_uri}"
360
+ f"Verification failed: Path '{remote_path}' does not exist on system '{system_id}'. Translated URI: {input_uri}"
334
361
  ) from e
335
362
  else:
336
363
  # Re-raise other Tapis errors encountered during verification
337
364
  raise FileOperationError(
338
- f"Verification error for path '{decoded_remote_path}' on system '{system_id}': {e}"
365
+ f"Verification error for path '{remote_path}' on system '{system_id}': {e}"
339
366
  ) from e
340
367
  except (
341
368
  ValueError
@@ -379,8 +406,12 @@ def upload_file(t: Tapis, local_path: str, remote_uri: str):
379
406
  print(
380
407
  f"Uploading '{local_path}' to system '{system_id}' at path '{dest_path}'..."
381
408
  )
409
+ # URL-encode the destination path for API call
410
+ encoded_dest_path = _safe_quote(dest_path)
382
411
  t.upload(
383
- system_id=system_id, source_file_path=local_path, dest_file_path=dest_path
412
+ system_id=system_id,
413
+ source_file_path=local_path,
414
+ dest_file_path=encoded_dest_path,
384
415
  )
385
416
  print("Upload complete.")
386
417
  except BaseTapyException as e:
@@ -424,8 +455,10 @@ def download_file(t: Tapis, remote_uri: str, local_path: str):
424
455
  os.makedirs(local_dir, exist_ok=True)
425
456
  # Use getContents which returns the raw bytes
426
457
  # Set stream=True for potentially large files
458
+ # URL-encode the source path for API call
459
+ encoded_source_path = _safe_quote(source_path)
427
460
  response = t.files.getContents(
428
- systemId=system_id, path=source_path, stream=True
461
+ systemId=system_id, path=encoded_source_path, stream=True
429
462
  )
430
463
 
431
464
  # Write the streamed content to the local file
@@ -477,8 +510,10 @@ def list_files(
477
510
  try:
478
511
  system_id, path = _parse_tapis_uri(remote_uri)
479
512
  print(f"Listing files in system '{system_id}' at path '{path}'...")
513
+ # URL-encode the path for API call
514
+ encoded_path = _safe_quote(path)
480
515
  results = t.files.listFiles(
481
- systemId=system_id, path=path, limit=limit, offset=offset
516
+ systemId=system_id, path=encoded_path, limit=limit, offset=offset
482
517
  )
483
518
  print(f"Found {len(results)} items.")
484
519
  return results
@@ -1007,9 +1007,7 @@ class SubmittedJob:
1007
1007
  details = self._get_details()
1008
1008
  if details.archiveSystemId and details.archiveSystemDir:
1009
1009
  archive_path = details.archiveSystemDir.lstrip("/")
1010
- return (
1011
- f"tapis://{details.archiveSystemId}/{urllib.parse.quote(archive_path)}"
1012
- )
1010
+ return f"tapis://{details.archiveSystemId}/{archive_path}"
1013
1011
  return None
1014
1012
 
1015
1013
  def list_outputs(
@@ -1048,7 +1046,7 @@ class SubmittedJob:
1048
1046
  full_archive_path = os.path.join(details.archiveSystemDir, path.lstrip("/"))
1049
1047
  full_archive_path = os.path.normpath(full_archive_path).lstrip("/")
1050
1048
  try:
1051
- archive_base_uri = f"tapis://{details.archiveSystemId}/{urllib.parse.quote(full_archive_path)}"
1049
+ archive_base_uri = f"tapis://{details.archiveSystemId}/{full_archive_path}"
1052
1050
  from .files import list_files
1053
1051
 
1054
1052
  return list_files(self._tapis, archive_base_uri, limit=limit, offset=offset)
@@ -1084,9 +1082,7 @@ class SubmittedJob:
1084
1082
  details.archiveSystemDir, remote_path.lstrip("/")
1085
1083
  )
1086
1084
  full_archive_path = os.path.normpath(full_archive_path).lstrip("/")
1087
- remote_uri = (
1088
- f"tapis://{details.archiveSystemId}/{urllib.parse.quote(full_archive_path)}"
1089
- )
1085
+ remote_uri = f"tapis://{details.archiveSystemId}/{full_archive_path}"
1090
1086
  try:
1091
1087
  from .files import download_file
1092
1088
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dapi"
3
- version = "0.4.5"
3
+ version = "0.4.7"
4
4
  description = "DesignSafe API"
5
5
  authors = [
6
6
  "Krishna Kumar <krishnak@utexas.edu>",
@@ -38,6 +38,7 @@ mkdocs-material = "^9.6.14"
38
38
  mkdocstrings = "^0.29.1"
39
39
  mkdocs-autorefs = "^1.4.2"
40
40
  griffe = "^1.7.3"
41
+ mkdocstrings-python = "^1.16.12"
41
42
 
42
43
  [tool.poetry.group.dev.dependencies]
43
44
  pytest = "^7.4.2"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes