latch 2.32.0__tar.gz → 2.32.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 (161) hide show
  1. {latch-2.32.0/latch.egg-info → latch-2.32.2}/PKG-INFO +1 -1
  2. {latch-2.32.0 → latch-2.32.2}/latch/types/directory.py +1 -1
  3. {latch-2.32.0 → latch-2.32.2}/latch/types/file.py +1 -1
  4. {latch-2.32.0 → latch-2.32.2/latch.egg-info}/PKG-INFO +1 -1
  5. {latch-2.32.0 → latch-2.32.2}/latch.egg-info/SOURCES.txt +3 -1
  6. {latch-2.32.0 → latch-2.32.2}/latch_cli/click_utils.py +4 -0
  7. {latch-2.32.0 → latch-2.32.2}/latch_cli/main.py +21 -93
  8. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/autocomplete.py +12 -8
  9. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/download.py +1 -1
  10. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/ldata_utils.py +1 -1
  11. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/main.py +1 -1
  12. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/remote_copy.py +1 -1
  13. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/throttle.py +1 -4
  14. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/upload.py +1 -11
  15. latch-2.32.2/latch_cli/services/init/__pycache__/__init__.cpython-311.pyc +0 -0
  16. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/__pycache__/init.cpython-310.pyc +0 -0
  17. latch-2.32.2/latch_cli/services/init/__pycache__/init.cpython-311.pyc +0 -0
  18. latch-2.32.2/latch_cli/services/ls.py +198 -0
  19. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/move.py +1 -5
  20. latch-2.32.0/latch_cli/services/cp/path_utils.py → latch-2.32.2/latch_cli/utils/path.py +9 -5
  21. {latch-2.32.0 → latch-2.32.2}/setup.py +1 -1
  22. latch-2.32.0/latch_cli/services/ls.py +0 -55
  23. {latch-2.32.0 → latch-2.32.2}/LICENSE +0 -0
  24. {latch-2.32.0 → latch-2.32.2}/MANIFEST.in +0 -0
  25. {latch-2.32.0 → latch-2.32.2}/README.md +0 -0
  26. {latch-2.32.0 → latch-2.32.2}/latch/__init__.py +0 -0
  27. {latch-2.32.0 → latch-2.32.2}/latch/account.py +0 -0
  28. {latch-2.32.0 → latch-2.32.2}/latch/functions/__init__.py +0 -0
  29. {latch-2.32.0 → latch-2.32.2}/latch/functions/messages.py +0 -0
  30. {latch-2.32.0 → latch-2.32.2}/latch/functions/operators.py +0 -0
  31. {latch-2.32.0 → latch-2.32.2}/latch/functions/secrets.py +0 -0
  32. {latch-2.32.0 → latch-2.32.2}/latch/registry/__init__.py +0 -0
  33. {latch-2.32.0 → latch-2.32.2}/latch/registry/project.py +0 -0
  34. {latch-2.32.0 → latch-2.32.2}/latch/registry/record.py +0 -0
  35. {latch-2.32.0 → latch-2.32.2}/latch/registry/table.py +0 -0
  36. {latch-2.32.0 → latch-2.32.2}/latch/registry/types.py +0 -0
  37. {latch-2.32.0 → latch-2.32.2}/latch/registry/upstream_types/__init__.py +0 -0
  38. {latch-2.32.0 → latch-2.32.2}/latch/registry/upstream_types/types.py +0 -0
  39. {latch-2.32.0 → latch-2.32.2}/latch/registry/upstream_types/values.py +0 -0
  40. {latch-2.32.0 → latch-2.32.2}/latch/registry/utils.py +0 -0
  41. {latch-2.32.0 → latch-2.32.2}/latch/resources/__init__.py +0 -0
  42. {latch-2.32.0 → latch-2.32.2}/latch/resources/conditional.py +0 -0
  43. {latch-2.32.0 → latch-2.32.2}/latch/resources/launch_plan.py +0 -0
  44. {latch-2.32.0 → latch-2.32.2}/latch/resources/map_tasks.py +0 -0
  45. {latch-2.32.0 → latch-2.32.2}/latch/resources/reference_workflow.py +0 -0
  46. {latch-2.32.0 → latch-2.32.2}/latch/resources/tasks.py +0 -0
  47. {latch-2.32.0 → latch-2.32.2}/latch/resources/workflow.py +0 -0
  48. {latch-2.32.0 → latch-2.32.2}/latch/types/__init__.py +0 -0
  49. {latch-2.32.0 → latch-2.32.2}/latch/types/glob.py +0 -0
  50. {latch-2.32.0 → latch-2.32.2}/latch/types/json.py +0 -0
  51. {latch-2.32.0 → latch-2.32.2}/latch/types/metadata.py +0 -0
  52. {latch-2.32.0 → latch-2.32.2}/latch/types/utils.py +0 -0
  53. {latch-2.32.0 → latch-2.32.2}/latch/verified/__init__.py +0 -0
  54. {latch-2.32.0 → latch-2.32.2}/latch/verified/deseq2.py +0 -0
  55. {latch-2.32.0 → latch-2.32.2}/latch/verified/mafft.py +0 -0
  56. {latch-2.32.0 → latch-2.32.2}/latch/verified/pathway.py +0 -0
  57. {latch-2.32.0 → latch-2.32.2}/latch/verified/rnaseq.py +0 -0
  58. {latch-2.32.0 → latch-2.32.2}/latch/verified/trim_galore.py +0 -0
  59. {latch-2.32.0 → latch-2.32.2}/latch.egg-info/dependency_links.txt +0 -0
  60. {latch-2.32.0 → latch-2.32.2}/latch.egg-info/entry_points.txt +0 -0
  61. {latch-2.32.0 → latch-2.32.2}/latch.egg-info/requires.txt +0 -0
  62. {latch-2.32.0 → latch-2.32.2}/latch.egg-info/top_level.txt +0 -0
  63. {latch-2.32.0 → latch-2.32.2}/latch_cli/__init__.py +0 -0
  64. {latch-2.32.0 → latch-2.32.2}/latch_cli/auth/__init__.py +0 -0
  65. {latch-2.32.0 → latch-2.32.2}/latch_cli/auth/csrf.py +0 -0
  66. {latch-2.32.0 → latch-2.32.2}/latch_cli/auth/oauth2.py +0 -0
  67. {latch-2.32.0 → latch-2.32.2}/latch_cli/auth/pkce.py +0 -0
  68. {latch-2.32.0 → latch-2.32.2}/latch_cli/auth/utils.py +0 -0
  69. {latch-2.32.0 → latch-2.32.2}/latch_cli/centromere/__init__.py +0 -0
  70. {latch-2.32.0 → latch-2.32.2}/latch_cli/centromere/ctx.py +0 -0
  71. {latch-2.32.0 → latch-2.32.2}/latch_cli/centromere/utils.py +0 -0
  72. {latch-2.32.0 → latch-2.32.2}/latch_cli/constants.py +0 -0
  73. {latch-2.32.0 → latch-2.32.2}/latch_cli/docker_utils/__init__.py +0 -0
  74. {latch-2.32.0 → latch-2.32.2}/latch_cli/exceptions/__init__.py +0 -0
  75. {latch-2.32.0 → latch-2.32.2}/latch_cli/exceptions/cache.py +0 -0
  76. {latch-2.32.0 → latch-2.32.2}/latch_cli/exceptions/errors.py +0 -0
  77. {latch-2.32.0 → latch-2.32.2}/latch_cli/exceptions/handler.py +0 -0
  78. {latch-2.32.0 → latch-2.32.2}/latch_cli/exceptions/traceback.py +0 -0
  79. {latch-2.32.0 → latch-2.32.2}/latch_cli/menus.py +0 -0
  80. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/__init__.py +0 -0
  81. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/__init__.py +0 -0
  82. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/config.py +0 -0
  83. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/exceptions.py +0 -0
  84. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/glob.py +0 -0
  85. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/manager.py +0 -0
  86. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/progress.py +0 -0
  87. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/cp/utils.py +0 -0
  88. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/deprecated/__init__.py +0 -0
  89. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/deprecated/mkdir.py +0 -0
  90. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/deprecated/rm.py +0 -0
  91. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/deprecated/touch.py +0 -0
  92. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/execute.py +0 -0
  93. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/get.py +0 -0
  94. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/get_executions.py +0 -0
  95. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/get_params.py +0 -0
  96. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/__init__.py +0 -0
  97. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/__pycache__/__init__.cpython-310.pyc +0 -0
  98. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/__pycache__/__init__.cpython-38.pyc +0 -0
  99. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/__pycache__/init.cpython-38.pyc +0 -0
  100. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/assemble_and_sort/.env +0 -0
  101. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/assemble_and_sort/LICENSE +0 -0
  102. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/assemble_and_sort/README.md +0 -0
  103. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/assemble_and_sort/__init__.py +0 -0
  104. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/assemble_and_sort/__pycache__/__init__.cpython-310.pyc +0 -0
  105. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/assemble_and_sort/assemble.py +0 -0
  106. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/assemble_and_sort/sort.py +0 -0
  107. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/assemble_and_sort/system-requirements.txt +0 -0
  108. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/common/.dockerignore +0 -0
  109. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/example_conda/__init__.py +0 -0
  110. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/example_conda/__pycache__/__init__.cpython-310.pyc +0 -0
  111. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/example_conda/conda_task.py +0 -0
  112. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/example_conda/environment.yaml +0 -0
  113. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/example_docker/__init__.py +0 -0
  114. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/example_docker/task.py +0 -0
  115. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/example_nfcore/Dockerfile +0 -0
  116. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/example_nfcore/__init__.py +0 -0
  117. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/example_nfcore/task.py +0 -0
  118. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/example_r/__init__.py +0 -0
  119. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/example_r/__pycache__/__init__.cpython-310.pyc +0 -0
  120. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/example_r/environment.R +0 -0
  121. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/example_r/r_task.py +0 -0
  122. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/init.py +0 -0
  123. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/template/LICENSE +0 -0
  124. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/template/README.md +0 -0
  125. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/template/__init__.py +0 -0
  126. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/template/__pycache__/__init__.cpython-310.pyc +0 -0
  127. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/init/template/task.py +0 -0
  128. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/launch.py +0 -0
  129. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/local_dev.py +0 -0
  130. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/local_dev_old.py +0 -0
  131. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/login.py +0 -0
  132. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/open_file.py +0 -0
  133. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/preview.py +0 -0
  134. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/register/__init__.py +0 -0
  135. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/register/constants.py +0 -0
  136. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/register/register.py +0 -0
  137. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/register/utils.py +0 -0
  138. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/stop_pod.py +0 -0
  139. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/test_data/__init__.py +0 -0
  140. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/test_data/ls.py +0 -0
  141. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/test_data/remove.py +0 -0
  142. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/test_data/upload.py +0 -0
  143. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/test_data/utils.py +0 -0
  144. {latch-2.32.0 → latch-2.32.2}/latch_cli/services/workspace.py +0 -0
  145. {latch-2.32.0 → latch-2.32.2}/latch_cli/snakemake/__init__.py +0 -0
  146. {latch-2.32.0 → latch-2.32.2}/latch_cli/snakemake/serialize.py +0 -0
  147. {latch-2.32.0 → latch-2.32.2}/latch_cli/snakemake/serialize_utils.py +0 -0
  148. {latch-2.32.0 → latch-2.32.2}/latch_cli/snakemake/single_task_snakemake.py +0 -0
  149. {latch-2.32.0 → latch-2.32.2}/latch_cli/snakemake/workflow.py +0 -0
  150. {latch-2.32.0 → latch-2.32.2}/latch_cli/tinyrequests.py +0 -0
  151. {latch-2.32.0 → latch-2.32.2}/latch_cli/tui/__init__.py +0 -0
  152. {latch-2.32.0 → latch-2.32.2}/latch_cli/utils/__init__.py +0 -0
  153. {latch-2.32.0 → latch-2.32.2}/latch_cli/workflow_config.py +0 -0
  154. {latch-2.32.0 → latch-2.32.2}/pyproject.toml +0 -0
  155. {latch-2.32.0 → latch-2.32.2}/setup.cfg +0 -0
  156. {latch-2.32.0 → latch-2.32.2}/tests/__init__.py +0 -0
  157. {latch-2.32.0 → latch-2.32.2}/tests/fixtures.py +0 -0
  158. {latch-2.32.0 → latch-2.32.2}/tests/test_cli.py +0 -0
  159. {latch-2.32.0 → latch-2.32.2}/tests/test_launch.py +0 -0
  160. {latch-2.32.0 → latch-2.32.2}/tests/test_login.py +0 -0
  161. {latch-2.32.0 → latch-2.32.2}/tests/test_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: latch
3
- Version: 2.32.0
3
+ Version: 2.32.2
4
4
  Summary: The Latch SDK
5
5
  Author-email: kenny@latch.bio
6
6
  Classifier: Programming Language :: Python :: 3.8
@@ -17,8 +17,8 @@ from typing_extensions import Annotated
17
17
 
18
18
  from latch.types.file import LatchFile
19
19
  from latch.types.utils import _is_valid_url
20
- from latch_cli.services.cp.path_utils import normalize_path
21
20
  from latch_cli.utils import urljoins
21
+ from latch_cli.utils.path import normalize_path
22
22
 
23
23
 
24
24
  class Child(TypedDict):
@@ -13,7 +13,7 @@ from latch_sdk_gql.execute import execute
13
13
  from typing_extensions import Annotated
14
14
 
15
15
  from latch.types.utils import _is_valid_url
16
- from latch_cli.services.cp.path_utils import normalize_path
16
+ from latch_cli.utils.path import normalize_path
17
17
 
18
18
  is_absolute_node_path = re.compile(r"^(latch)?://\d+.node(/)?$")
19
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: latch
3
- Version: 2.32.0
3
+ Version: 2.32.2
4
4
  Summary: The Latch SDK
5
5
  Author-email: kenny@latch.bio
6
6
  Classifier: Programming Language :: Python :: 3.8
@@ -89,7 +89,6 @@ latch_cli/services/cp/glob.py
89
89
  latch_cli/services/cp/ldata_utils.py
90
90
  latch_cli/services/cp/main.py
91
91
  latch_cli/services/cp/manager.py
92
- latch_cli/services/cp/path_utils.py
93
92
  latch_cli/services/cp/progress.py
94
93
  latch_cli/services/cp/remote_copy.py
95
94
  latch_cli/services/cp/throttle.py
@@ -102,8 +101,10 @@ latch_cli/services/deprecated/touch.py
102
101
  latch_cli/services/init/__init__.py
103
102
  latch_cli/services/init/init.py
104
103
  latch_cli/services/init/__pycache__/__init__.cpython-310.pyc
104
+ latch_cli/services/init/__pycache__/__init__.cpython-311.pyc
105
105
  latch_cli/services/init/__pycache__/__init__.cpython-38.pyc
106
106
  latch_cli/services/init/__pycache__/init.cpython-310.pyc
107
+ latch_cli/services/init/__pycache__/init.cpython-311.pyc
107
108
  latch_cli/services/init/__pycache__/init.cpython-38.pyc
108
109
  latch_cli/services/init/assemble_and_sort/.env
109
110
  latch_cli/services/init/assemble_and_sort/LICENSE
@@ -148,6 +149,7 @@ latch_cli/snakemake/single_task_snakemake.py
148
149
  latch_cli/snakemake/workflow.py
149
150
  latch_cli/tui/__init__.py
150
151
  latch_cli/utils/__init__.py
152
+ latch_cli/utils/path.py
151
153
  tests/__init__.py
152
154
  tests/fixtures.py
153
155
  tests/test_cli.py
@@ -102,6 +102,10 @@ def patch():
102
102
  click.UsageError.show = colored_usage_error_show
103
103
 
104
104
 
105
+ def bold(s: str) -> str:
106
+ return f"{AnsiCodes.bold}{s}{AnsiCodes.reset}"
107
+
108
+
105
109
  class AnsiCodes:
106
110
  bold = "\x1b[1m"
107
111
  reset = "\x1b[22m"
@@ -2,10 +2,9 @@
2
2
 
3
3
  import os
4
4
  import sys
5
- from collections import OrderedDict
6
5
  from pathlib import Path
7
6
  from textwrap import dedent
8
- from typing import List, Optional, Union
7
+ from typing import List, Optional, Tuple, Union
9
8
 
10
9
  import click
11
10
  from packaging.version import parse as parse_version
@@ -45,17 +44,17 @@ def main():
45
44
  """
46
45
  try:
47
46
  get_auth_header()
48
- except AuthenticationError:
47
+ except AuthenticationError as e:
49
48
  click.secho(
50
49
  dedent("""
51
50
  Unable to authenticate with Latch.
52
51
 
53
52
  If you are on a machine with a browser, run `latch login`.
54
53
  If not, navigate to `https://console.latch.bio/settings/developer` on a different machine, select `Access Tokens`, and copy your `API Key` to `~/.latch/token` on this machine.
55
- """),
54
+ """).strip("\n"),
56
55
  fg="red",
57
56
  )
58
- raise click.exceptions.Exit()
57
+ raise click.exceptions.Exit() from e
59
58
 
60
59
  local_ver = parse_version(get_local_package_version())
61
60
  latest_ver = parse_version(get_latest_package_version())
@@ -357,103 +356,32 @@ def mv(src: str, dest: str):
357
356
  is_flag=True,
358
357
  default=False,
359
358
  )
360
- # todo(maximsmol): enable once ls uses gql and supports new paths
361
- # @click.argument("remote_directories", nargs=-1, shell_complete=remote_complete)
362
- @click.argument("remote_directories", nargs=-1)
363
- def ls(group_directories_first: bool, remote_directories: Union[None, List[str]]):
359
+ @click.argument("paths", nargs=-1, shell_complete=remote_complete)
360
+ def ls(paths: Tuple[str], group_directories_first: bool):
364
361
  """
365
362
  List the contents of a Latch Data directory
366
363
  """
367
364
 
368
- crash_handler.message = f"Unable to display contents of {remote_directories}"
365
+ crash_handler.message = f"Unable to display contents of {paths}"
369
366
  crash_handler.pkg_root = str(Path.cwd())
370
367
 
371
- from datetime import datetime
372
-
373
368
  from latch_cli.services.ls import ls
374
- from latch_cli.utils import with_si_suffix
375
369
 
376
370
  # If the user doesn't provide any arguments, default to root
377
- if not remote_directories:
378
- remote_directories = ["latch:///"]
379
-
380
- for remote_directory in remote_directories:
381
- if len(remote_directories) > 1:
382
- click.echo(f"{remote_directory}:")
383
-
384
- output = ls(remote_directory)
385
-
386
- output.sort(key=lambda row: row["name"])
387
- if group_directories_first:
388
- output.sort(key=lambda row: row["type"])
389
-
390
- formatted = []
391
- for row in output:
392
- vals = {
393
- "contentSize": (
394
- click.style(
395
- with_si_suffix(int(row["contentSize"]), suffix="", styled=True),
396
- fg="bright_green",
397
- )
398
- if row["contentSize"] != "-" and row["type"] != "dir"
399
- else click.style("-", dim=True)
400
- ),
401
- "modifyTime": (
402
- click.style(
403
- datetime.fromisoformat(row["modifyTime"]).strftime(
404
- "%d %b %H:%M"
405
- ),
406
- fg="blue",
407
- )
408
- if row["modifyTime"] != "-" and row["type"] != "dir"
409
- else click.style("-", dim=True)
410
- ),
411
- "name": (
412
- row["name"] if len(row["name"]) <= 50 else f"{row['name'][:47]}..."
413
- ),
414
- }
415
-
416
- if row["type"] == "dir":
417
- vals["name"] = (
418
- click.style(row["name"], fg="bright_blue", bold=True) + "/"
419
- )
420
-
421
- formatted.append(vals)
422
-
423
- columns = OrderedDict(
424
- contentSize="Size", modifyTime="Date Modified", name="Name"
425
- )
371
+ if len(paths) == 0:
372
+ paths = ("/",)
426
373
 
427
- column_width = {key: len(title) for key, title in columns.items()}
428
- for row in formatted:
429
- for key in columns:
430
- column_width[key] = max(column_width[key], len(click.unstyle(row[key])))
431
-
432
- def pad_styled(x: str, l: int, align_right=False):
433
- cur = len(click.unstyle(x))
434
-
435
- pad = " " * (l - cur)
436
- if align_right:
437
- return pad + x
438
- return x + pad
439
-
440
- click.echo(
441
- " ".join(
442
- pad_styled(
443
- click.style(title, underline=True),
444
- column_width[key],
445
- key == "contentSize",
446
- )
447
- for key, title in columns.items()
448
- )
374
+ for path in paths:
375
+ if len(paths) > 1:
376
+ click.echo(f"{path}:")
377
+
378
+ ls(
379
+ path,
380
+ group_directories_first=group_directories_first,
449
381
  )
450
- for row in formatted:
451
- click.echo(
452
- " ".join(
453
- pad_styled(row[k], column_width[k], k == "contentSize")
454
- for k in columns
455
- )
456
- )
382
+
383
+ if len(paths) > 1:
384
+ click.echo("")
457
385
 
458
386
 
459
387
  @main.command("launch")
@@ -576,7 +504,7 @@ def mkdir(remote_directory: str):
576
504
  from latch_cli.services.deprecated.mkdir import mkdir
577
505
 
578
506
  click.secho(
579
- f"Warning: `latch mkdir` is deprecated and will be removed soon.",
507
+ "Warning: `latch mkdir` is deprecated and will be removed soon.",
580
508
  fg="yellow",
581
509
  )
582
510
  mkdir(remote_directory)
@@ -593,7 +521,7 @@ def touch(remote_file: str):
593
521
  from latch_cli.services.deprecated.touch import touch
594
522
 
595
523
  click.secho(
596
- f"Warning: `latch touch` is deprecated and will be removed soon.",
524
+ "Warning: `latch touch` is deprecated and will be removed soon.",
597
525
  fg="yellow",
598
526
  )
599
527
  touch(remote_file)
@@ -1,10 +1,6 @@
1
- try:
2
- from functools import cache
3
- except ImportError:
4
- from functools import lru_cache as cache
5
-
6
1
  import os
7
2
  import re
3
+ from functools import lru_cache
8
4
  from pathlib import Path
9
5
  from typing import List
10
6
 
@@ -16,13 +12,17 @@ from latch_cli.services.cp.ldata_utils import (
16
12
  _get_known_domains_for_account,
17
13
  )
18
14
 
15
+ cache = lru_cache(maxsize=None)
19
16
  completion_type = re.compile(
20
17
  r"""
21
18
  ^
22
19
  (latch)?
23
20
  :/?/?
24
21
  (?P<domain>[^/]*)
25
- (?P<path>/.*)?
22
+ (
23
+ (?P<parent>(/[^/]*)*)?
24
+ (?P<path>/[^/]*)
25
+ )?
26
26
  $
27
27
  """,
28
28
  re.VERBOSE,
@@ -30,7 +30,10 @@ completion_type = re.compile(
30
30
 
31
31
 
32
32
  def complete(
33
- ctx: click.Context, param: click.Argument, incomplete: str, allow_local: bool = True
33
+ ctx: click.Context,
34
+ param: click.Argument,
35
+ incomplete: str,
36
+ allow_local: bool = True,
34
37
  ) -> List[sc.CompletionItem]:
35
38
  match = completion_type.match(incomplete)
36
39
 
@@ -81,9 +84,10 @@ def _complete_local_path(incomplete: str) -> List[sc.CompletionItem]:
81
84
  @cache
82
85
  def _complete_remote_path(match: re.Match[str]) -> List[sc.CompletionItem]:
83
86
  domain = match["domain"]
87
+ parent = match["parent"]
84
88
  path = match["path"][1:]
85
89
 
86
- parent = f"://{domain}"
90
+ parent = f"://{domain}{parent}"
87
91
  if match[0].startswith("latch"):
88
92
  parent = f"latch{parent}"
89
93
 
@@ -14,10 +14,10 @@ from latch_cli.constants import Units
14
14
  from latch_cli.services.cp.config import CPConfig, Progress
15
15
  from latch_cli.services.cp.ldata_utils import LDataNodeType, get_node_data
16
16
  from latch_cli.services.cp.manager import CPStateManager
17
- from latch_cli.services.cp.path_utils import normalize_path
18
17
  from latch_cli.services.cp.progress import ProgressBars, get_free_index
19
18
  from latch_cli.services.cp.utils import get_max_workers, human_readable_time
20
19
  from latch_cli.utils import get_auth_header, with_si_suffix
20
+ from latch_cli.utils.path import normalize_path
21
21
 
22
22
 
23
23
  class GetSignedUrlData(TypedDict):
@@ -12,7 +12,7 @@ import graphql.language as l
12
12
  from latch_sdk_gql.execute import execute
13
13
  from latch_sdk_gql.utils import _name_node, _parse_selection
14
14
 
15
- from latch_cli.services.cp.path_utils import get_path_error, normalize_path
15
+ from latch_cli.utils.path import get_path_error, normalize_path
16
16
 
17
17
 
18
18
  class LDataNodeType(str, Enum):
@@ -4,9 +4,9 @@ from typing import List
4
4
  from latch_cli.services.cp.config import CPConfig, Progress
5
5
  from latch_cli.services.cp.download import download
6
6
  from latch_cli.services.cp.glob import expand_pattern
7
- from latch_cli.services.cp.path_utils import is_remote_path
8
7
  from latch_cli.services.cp.remote_copy import remote_copy
9
8
  from latch_cli.services.cp.upload import upload
9
+ from latch_cli.utils.path import is_remote_path
10
10
 
11
11
 
12
12
  # todo(ayush): come up with a better behavior scheme than unix cp
@@ -4,7 +4,7 @@ from gql.transport.exceptions import TransportQueryError
4
4
  from latch_sdk_gql.execute import execute
5
5
 
6
6
  from latch_cli.services.cp.ldata_utils import LDataNodeType, get_node_data
7
- from latch_cli.services.cp.path_utils import get_name_from_path, get_path_error
7
+ from latch_cli.utils.path import get_name_from_path, get_path_error
8
8
 
9
9
 
10
10
  # todo(ayush): figure out how to do progress for this
@@ -1,7 +1,4 @@
1
- from multiprocessing.managers import BaseManager
2
- from typing import Type
3
-
4
- from attr import dataclass
1
+ from dataclasses import dataclass
5
2
 
6
3
 
7
4
  @dataclass
@@ -1,9 +1,5 @@
1
- import datetime
2
- import json
3
- import logging
4
1
  import math
5
2
  import mimetypes
6
- import multiprocessing
7
3
  import os
8
4
  import random
9
5
  import time
@@ -25,11 +21,11 @@ from latch_cli.constants import latch_constants, units
25
21
  from latch_cli.services.cp.config import CPConfig, Progress
26
22
  from latch_cli.services.cp.ldata_utils import LDataNodeType, get_node_data
27
23
  from latch_cli.services.cp.manager import CPStateManager
28
- from latch_cli.services.cp.path_utils import normalize_path
29
24
  from latch_cli.services.cp.progress import ProgressBars
30
25
  from latch_cli.services.cp.throttle import Throttle
31
26
  from latch_cli.services.cp.utils import get_max_workers, human_readable_time
32
27
  from latch_cli.utils import get_auth_header, urljoins, with_si_suffix
28
+ from latch_cli.utils.path import normalize_path
33
29
 
34
30
  if TYPE_CHECKING:
35
31
  PathQueueType: TypeAlias = "Queue[Optional[Path]]"
@@ -37,12 +33,6 @@ if TYPE_CHECKING:
37
33
  PartsBySrcType: TypeAlias = DictProxy[Path, ListProxy["CompletedPart"]]
38
34
  UploadInfoBySrcType: TypeAlias = DictProxy[Path, "StartUploadReturnType"]
39
35
 
40
- logging.basicConfig()
41
- multiprocessing.get_logger().setLevel(logging.DEBUG)
42
-
43
-
44
- start_upload_batch_size = 100
45
-
46
36
 
47
37
  class EmptyUploadData(TypedDict):
48
38
  version_id: str
@@ -0,0 +1,198 @@
1
+ """Service to list files in a remote directory."""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from textwrap import dedent
6
+ from typing import List, Optional, TypedDict
7
+
8
+ import click
9
+ import gql
10
+ from latch_sdk_gql.execute import execute
11
+
12
+ from latch_cli.click_utils import bold
13
+ from latch_cli.services.cp.ldata_utils import LDataNodeType
14
+ from latch_cli.utils import with_si_suffix
15
+ from latch_cli.utils.path import normalize_path
16
+
17
+
18
+ class _LdataObjectMeta(TypedDict):
19
+ modifyTime: Optional[str]
20
+ contentSize: Optional[int]
21
+
22
+
23
+ class _Child(TypedDict):
24
+ name: str
25
+ ldataObjectMeta: Optional[_LdataObjectMeta]
26
+ type: str
27
+
28
+
29
+ class _Node(TypedDict):
30
+ child: _Child
31
+
32
+
33
+ class _ChildLdataTreeEdges(TypedDict):
34
+ nodes: List[_Node]
35
+
36
+
37
+ class _FinalLinkTarget(TypedDict):
38
+ childLdataTreeEdges: _ChildLdataTreeEdges
39
+
40
+
41
+ class _LdataResolvePathData(TypedDict):
42
+ name: str
43
+ type: str
44
+ ldataObjectMeta: Optional[_LdataObjectMeta]
45
+ finalLinkTarget: _FinalLinkTarget
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class _Row:
50
+ name: str
51
+ type: LDataNodeType
52
+ size: Optional[int]
53
+ modify_time: Optional[datetime]
54
+
55
+
56
+ def ls(path: str, *, group_directories_first: bool = False):
57
+ """Lists the children of a remote directory in Latch.
58
+
59
+ Args:
60
+ path: A valid remote path
61
+ group_directories_first: Option to display directories/links before
62
+ objects
63
+
64
+ This function will list all of the entites under the remote directory
65
+ specified in the path `path`. Will error if the path is invalid
66
+ or the directory doesn't exist.
67
+
68
+ Examples:
69
+ >>> ls("")
70
+ # Lists all entities in the user's root directory
71
+ >>> ls("latch:///dir1/dir2/dir_name")
72
+ # Lists all entities inside dir1/dir2/dir_name
73
+ """
74
+ if path == "":
75
+ path = "/"
76
+
77
+ normalized_path = normalize_path(path, assume_remote=True)
78
+
79
+ query = execute(
80
+ gql.gql("""
81
+ query LdataInfo ($argPath: String!) {
82
+ accountInfoCurrent {
83
+ id
84
+ }
85
+ ldataResolvePathData(argPath: $argPath) {
86
+ name
87
+ ldataObjectMeta {
88
+ modifyTime
89
+ contentSize
90
+ }
91
+ type
92
+ finalLinkTarget {
93
+ childLdataTreeEdges(filter: {
94
+ child: {
95
+ removed: { equalTo: false },
96
+ pending: { equalTo: false }
97
+ }
98
+ }) {
99
+ nodes {
100
+ child {
101
+ name
102
+ ldataObjectMeta {
103
+ modifyTime
104
+ contentSize
105
+ }
106
+ type
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ """),
114
+ {"argPath": normalized_path},
115
+ )
116
+
117
+ res: Optional[_LdataResolvePathData] = query["ldataResolvePathData"]
118
+ acc_id: str = query["accountInfoCurrent"]["id"]
119
+
120
+ if res is None:
121
+ click.secho(
122
+ dedent(f"""
123
+ {bold(path)}: no such file or directory.
124
+
125
+ {bold("Check that:")}
126
+ 1. The target directory exists,
127
+ 2. Account {bold(acc_id)} has permissions to view the target directory, and
128
+ 3. The correct workspace is selected.
129
+
130
+ For privacy reasons, non-viewable objects and non-existent objects are indistinguishable.
131
+ """).strip("\n"),
132
+ fg="red",
133
+ )
134
+ return
135
+
136
+ nodes = res["finalLinkTarget"]["childLdataTreeEdges"]["nodes"]
137
+ if LDataNodeType(res["type"].lower()) == LDataNodeType.obj:
138
+ # ls object should just display the object's info
139
+ nodes.append({"child": res})
140
+
141
+ rows: List[_Row] = []
142
+ for node in nodes:
143
+ child = node["child"]
144
+
145
+ meta = child["ldataObjectMeta"]
146
+ size = modify_time = None
147
+ if meta is not None:
148
+ if meta["contentSize"] is not None:
149
+ size = int(meta["contentSize"])
150
+ if meta["modifyTime"] is not None:
151
+ modify_time = datetime.fromisoformat(meta["modifyTime"])
152
+
153
+ rows.append(
154
+ _Row(
155
+ name=child["name"],
156
+ type=LDataNodeType(child["type"].lower()),
157
+ size=size,
158
+ modify_time=modify_time,
159
+ )
160
+ )
161
+
162
+ rows.sort(key=lambda row: row.name)
163
+ if group_directories_first:
164
+ rows.sort(key=lambda row: 1 if row.type == LDataNodeType.obj else 0)
165
+
166
+ headers = [
167
+ " " + click.style("Size", underline=True),
168
+ click.style("Date Modified", underline=True),
169
+ click.style("Name", underline=True),
170
+ ]
171
+
172
+ click.echo(" ".join(headers))
173
+
174
+ for row in rows:
175
+ mt_str = f'{"-": <13}'
176
+ size_str = f'{"-": >6}'
177
+ if row.type == LDataNodeType.obj:
178
+ if row.modify_time is not None:
179
+ mt_str = click.style(
180
+ f'{row.modify_time.strftime("%d %b %H:%M"): <13}', fg="blue"
181
+ )
182
+
183
+ if row.size is not None:
184
+ size_str = with_si_suffix(row.size, suffix="")
185
+ size_str = click.style(f"{size_str: >6}", fg="bright_green")
186
+
187
+ name_str = row.name
188
+ if len(name_str) > 50:
189
+ name_str = f"{name_str[:47]}..."
190
+
191
+ if row.type != LDataNodeType.obj:
192
+ color = "bright_blue"
193
+ if row.type == LDataNodeType.link:
194
+ color = "bright_magenta"
195
+
196
+ name_str = click.style(f"{name_str}/", bold=True, fg=color)
197
+
198
+ click.echo(f"{size_str} {mt_str} {name_str}")
@@ -4,11 +4,7 @@ from gql.transport.exceptions import TransportQueryError
4
4
  from latch_sdk_gql.execute import execute
5
5
 
6
6
  from latch_cli.services.cp.ldata_utils import LDataNodeType, get_node_data
7
- from latch_cli.services.cp.path_utils import (
8
- get_name_from_path,
9
- get_path_error,
10
- is_remote_path,
11
- )
7
+ from latch_cli.utils.path import get_name_from_path, get_path_error, is_remote_path
12
8
 
13
9
 
14
10
  def move(
@@ -52,7 +52,11 @@ domain = re.compile(
52
52
  # ://domain/a/b/c => latch://domain/a/b/c
53
53
  # /a/b/c => file:///a/b/c
54
54
  # a/b/c => file://${pwd}/a/b/c
55
- def append_scheme(path: str) -> str:
55
+ #
56
+ # if assume_remote = True (i.e. in the ls case):
57
+ # /a/b/c => latch:///a/b/c
58
+ # a/b/c => latch:///a/b/c
59
+ def append_scheme(path: str, *, assume_remote: bool = False) -> str:
56
60
  match = scheme.match(path)
57
61
  if match is None:
58
62
  raise PathResolutionError(f"{path} is not in a valid format")
@@ -60,9 +64,9 @@ def append_scheme(path: str) -> str:
60
64
  if match["implicit_url"] is not None:
61
65
  path = f"latch{path}"
62
66
  elif match["absolute_path"] is not None:
63
- path = f"file://{path}"
67
+ path = f"latch://{path}" if assume_remote else f"file://{path}"
64
68
  elif match["relative_path"] is not None:
65
- path = f"file://{Path.cwd()}/{path}"
69
+ path = f"latch:///{path}" if assume_remote else f"file://{Path.cwd()}/{path}"
66
70
 
67
71
  return path
68
72
 
@@ -108,8 +112,8 @@ def is_account_relative(path: str) -> bool:
108
112
  return match["account_relative"] is not None
109
113
 
110
114
 
111
- def normalize_path(path: str) -> str:
112
- path = append_scheme(path)
115
+ def normalize_path(path: str, *, assume_remote: bool = False) -> str:
116
+ path = append_scheme(path, assume_remote=assume_remote)
113
117
 
114
118
  if path.startswith("file://"):
115
119
  return path
@@ -13,7 +13,7 @@ if cur_ver < (3, 8) or cur_ver > (3, 11):
13
13
 
14
14
  setup(
15
15
  name="latch",
16
- version="v2.32.0",
16
+ version="v2.32.2",
17
17
  author_email="kenny@latch.bio",
18
18
  description="The Latch SDK",
19
19
  packages=find_packages(),