modal 1.0.1.dev3__tar.gz → 1.0.1.dev5__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 (184) hide show
  1. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/PKG-INFO +1 -1
  2. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_output.py +15 -6
  3. modal-1.0.1.dev5/modal/_utils/time_utils.py +15 -0
  4. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/app.py +4 -2
  5. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/cluster.py +2 -1
  6. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/container.py +2 -1
  7. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/dict.py +2 -1
  8. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/network_file_system.py +2 -1
  9. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/queues.py +2 -1
  10. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/secret.py +2 -1
  11. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/utils.py +5 -15
  12. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/volume.py +4 -3
  13. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/client.py +3 -2
  14. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/client.pyi +2 -2
  15. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal.egg-info/PKG-INFO +1 -1
  16. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal.egg-info/SOURCES.txt +1 -0
  17. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_version/__init__.py +1 -1
  18. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/LICENSE +0 -0
  19. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/README.md +0 -0
  20. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/__init__.py +0 -0
  21. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/__main__.py +0 -0
  22. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_clustered_functions.py +0 -0
  23. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_clustered_functions.pyi +0 -0
  24. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_container_entrypoint.py +0 -0
  25. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_functions.py +0 -0
  26. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_ipython.py +0 -0
  27. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_location.py +0 -0
  28. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_object.py +0 -0
  29. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_partial_function.py +0 -0
  30. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_pty.py +0 -0
  31. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_resolver.py +0 -0
  32. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_resources.py +0 -0
  33. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_runtime/__init__.py +0 -0
  34. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_runtime/asgi.py +0 -0
  35. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_runtime/container_io_manager.py +0 -0
  36. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_runtime/container_io_manager.pyi +0 -0
  37. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_runtime/execution_context.py +0 -0
  38. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_runtime/execution_context.pyi +0 -0
  39. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  40. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_runtime/telemetry.py +0 -0
  41. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_runtime/user_code_imports.py +0 -0
  42. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_serialization.py +0 -0
  43. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_traceback.py +0 -0
  44. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_tunnel.py +0 -0
  45. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_tunnel.pyi +0 -0
  46. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_type_manager.py +0 -0
  47. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/__init__.py +0 -0
  48. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/app_utils.py +0 -0
  49. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/async_utils.py +0 -0
  50. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/blob_utils.py +0 -0
  51. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/bytes_io_segment_payload.py +0 -0
  52. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/deprecation.py +0 -0
  53. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/docker_utils.py +0 -0
  54. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/function_utils.py +0 -0
  55. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/git_utils.py +0 -0
  56. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/grpc_testing.py +0 -0
  57. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/grpc_utils.py +0 -0
  58. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/hash_utils.py +0 -0
  59. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/http_utils.py +0 -0
  60. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/jwt_utils.py +0 -0
  61. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/logger.py +0 -0
  62. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/mount_utils.py +0 -0
  63. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/name_utils.py +0 -0
  64. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/package_utils.py +0 -0
  65. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/pattern_utils.py +0 -0
  66. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/rand_pb_testing.py +0 -0
  67. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_utils/shell_utils.py +0 -0
  68. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_vendor/__init__.py +0 -0
  69. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  70. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_vendor/cloudpickle.py +0 -0
  71. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_vendor/tblib.py +0 -0
  72. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/_watcher.py +0 -0
  73. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/app.py +0 -0
  74. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/app.pyi +0 -0
  75. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/call_graph.py +0 -0
  76. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/__init__.py +0 -0
  77. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/_download.py +0 -0
  78. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/_traceback.py +0 -0
  79. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/config.py +0 -0
  80. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/entry_point.py +0 -0
  81. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/environment.py +0 -0
  82. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/import_refs.py +0 -0
  83. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/launch.py +0 -0
  84. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/profile.py +0 -0
  85. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/programs/__init__.py +0 -0
  86. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/programs/run_jupyter.py +0 -0
  87. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/programs/vscode.py +0 -0
  88. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/run.py +0 -0
  89. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cli/token.py +0 -0
  90. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cloud_bucket_mount.py +0 -0
  91. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cloud_bucket_mount.pyi +0 -0
  92. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cls.py +0 -0
  93. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/cls.pyi +0 -0
  94. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/config.py +0 -0
  95. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/container_process.py +0 -0
  96. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/container_process.pyi +0 -0
  97. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/dict.py +0 -0
  98. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/dict.pyi +0 -0
  99. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/environments.py +0 -0
  100. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/environments.pyi +0 -0
  101. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/exception.py +0 -0
  102. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/experimental/__init__.py +0 -0
  103. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/experimental/ipython.py +0 -0
  104. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/file_io.py +0 -0
  105. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/file_io.pyi +0 -0
  106. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/file_pattern_matcher.py +0 -0
  107. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/functions.py +0 -0
  108. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/functions.pyi +0 -0
  109. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/gpu.py +0 -0
  110. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/image.py +0 -0
  111. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/image.pyi +0 -0
  112. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/io_streams.py +0 -0
  113. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/io_streams.pyi +0 -0
  114. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/mount.py +0 -0
  115. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/mount.pyi +0 -0
  116. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/network_file_system.py +0 -0
  117. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/network_file_system.pyi +0 -0
  118. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/object.py +0 -0
  119. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/object.pyi +0 -0
  120. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/output.py +0 -0
  121. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/parallel_map.py +0 -0
  122. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/parallel_map.pyi +0 -0
  123. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/partial_function.py +0 -0
  124. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/partial_function.pyi +0 -0
  125. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/proxy.py +0 -0
  126. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/proxy.pyi +0 -0
  127. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/py.typed +0 -0
  128. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/queue.py +0 -0
  129. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/queue.pyi +0 -0
  130. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/requirements/2023.12.312.txt +0 -0
  131. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/requirements/2023.12.txt +0 -0
  132. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/requirements/2024.04.txt +0 -0
  133. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/requirements/2024.10.txt +0 -0
  134. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/requirements/PREVIEW.txt +0 -0
  135. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/requirements/README.md +0 -0
  136. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/requirements/base-images.json +0 -0
  137. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/retries.py +0 -0
  138. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/runner.py +0 -0
  139. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/runner.pyi +0 -0
  140. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/running_app.py +0 -0
  141. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/sandbox.py +0 -0
  142. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/sandbox.pyi +0 -0
  143. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/schedule.py +0 -0
  144. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/scheduler_placement.py +0 -0
  145. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/secret.py +0 -0
  146. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/secret.pyi +0 -0
  147. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/serving.py +0 -0
  148. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/serving.pyi +0 -0
  149. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/snapshot.py +0 -0
  150. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/snapshot.pyi +0 -0
  151. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/stream_type.py +0 -0
  152. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/token_flow.py +0 -0
  153. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/token_flow.pyi +0 -0
  154. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/volume.py +0 -0
  155. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal/volume.pyi +0 -0
  156. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal.egg-info/dependency_links.txt +0 -0
  157. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal.egg-info/entry_points.txt +0 -0
  158. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal.egg-info/requires.txt +0 -0
  159. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal.egg-info/top_level.txt +0 -0
  160. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_docs/__init__.py +0 -0
  161. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_docs/gen_cli_docs.py +0 -0
  162. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_docs/gen_reference_docs.py +0 -0
  163. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_docs/mdmd/__init__.py +0 -0
  164. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_docs/mdmd/mdmd.py +0 -0
  165. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_docs/mdmd/signatures.py +0 -0
  166. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/__init__.py +0 -0
  167. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/api.proto +0 -0
  168. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/api_grpc.py +0 -0
  169. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/api_pb2.py +0 -0
  170. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/api_pb2.pyi +0 -0
  171. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/api_pb2_grpc.py +0 -0
  172. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/api_pb2_grpc.pyi +0 -0
  173. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/modal_api_grpc.py +0 -0
  174. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/modal_options_grpc.py +0 -0
  175. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/options.proto +0 -0
  176. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/options_grpc.py +0 -0
  177. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/options_pb2.py +0 -0
  178. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/options_pb2.pyi +0 -0
  179. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/options_pb2_grpc.py +0 -0
  180. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/options_pb2_grpc.pyi +0 -0
  181. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_proto/py.typed +0 -0
  182. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/modal_version/__main__.py +0 -0
  183. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/pyproject.toml +0 -0
  184. {modal-1.0.1.dev3 → modal-1.0.1.dev5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.0.1.dev3
3
+ Version: 1.0.1.dev5
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -32,6 +32,7 @@ from rich.progress import (
32
32
  from rich.spinner import Spinner
33
33
  from rich.text import Text
34
34
 
35
+ from modal._utils.time_utils import timestamp_to_local
35
36
  from modal_proto import api_pb2
36
37
 
37
38
  from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, retry_transient_errors
@@ -81,22 +82,27 @@ def download_progress_bar() -> Progress:
81
82
  )
82
83
 
83
84
 
84
- class LineBufferedOutput(io.StringIO):
85
+ class LineBufferedOutput:
85
86
  """Output stream that buffers lines and passes them to a callback."""
86
87
 
87
88
  LINE_REGEX = re.compile("(\r\n|\r|\n)")
88
89
 
89
- def __init__(self, callback: Callable[[str], None]):
90
+ def __init__(self, callback: Callable[[str], None], show_timestamps: bool):
90
91
  self._callback = callback
91
92
  self._buf = ""
93
+ self._show_timestamps = show_timestamps
92
94
 
93
- def write(self, data: str):
94
- chunks = self.LINE_REGEX.split(self._buf + data)
95
+ def write(self, log: api_pb2.TaskLogs):
96
+ chunks = self.LINE_REGEX.split(self._buf + log.data)
95
97
 
96
98
  # re.split("(<exp>)") returns the matched groups, and also the separators.
97
99
  # e.g. re.split("(+)", "a+b") returns ["a", "+", "b"].
98
100
  # This means that chunks is guaranteed to be odd in length.
99
101
 
102
+ if self._show_timestamps:
103
+ for i in range(0, len(chunks) - 1, 2):
104
+ chunks[i] = f"{timestamp_to_local(log.timestamp)} {chunks[i]}"
105
+
100
106
  completed_lines = "".join(chunks[:-1])
101
107
  remainder = chunks[-1]
102
108
 
@@ -136,12 +142,14 @@ class OutputManager:
136
142
  _app_page_url: str | None
137
143
  _show_image_logs: bool
138
144
  _status_spinner_live: Live | None
145
+ _show_timestamps: bool
139
146
 
140
147
  def __init__(
141
148
  self,
142
149
  *,
143
150
  stdout: io.TextIOWrapper | None = None,
144
151
  status_spinner_text: str = "Running app...",
152
+ show_timestamps: bool = False,
145
153
  ):
146
154
  self._stdout = stdout or sys.stdout
147
155
  self._console = Console(file=stdout, highlight=False)
@@ -156,6 +164,7 @@ class OutputManager:
156
164
  self._app_page_url = None
157
165
  self._show_image_logs = False
158
166
  self._status_spinner_live = None
167
+ self._show_timestamps = show_timestamps
159
168
 
160
169
  @classmethod
161
170
  def disable(cls):
@@ -355,9 +364,9 @@ class OutputManager:
355
364
  async def put_log_content(self, log: api_pb2.TaskLogs):
356
365
  stream = self._line_buffers.get(log.file_descriptor)
357
366
  if stream is None:
358
- stream = LineBufferedOutput(functools.partial(self._print_log, log.file_descriptor))
367
+ stream = LineBufferedOutput(functools.partial(self._print_log, log.file_descriptor), self._show_timestamps)
359
368
  self._line_buffers[log.file_descriptor] = stream
360
- stream.write(log.data)
369
+ stream.write(log)
361
370
 
362
371
  def flush_lines(self):
363
372
  for stream in self._line_buffers.values():
@@ -0,0 +1,15 @@
1
+ # Copyright Modal Labs 2025
2
+ from datetime import datetime
3
+ from typing import Optional
4
+
5
+
6
+ def timestamp_to_local(ts: float, isotz: bool = True) -> Optional[str]:
7
+ if ts > 0:
8
+ locale_tz = datetime.now().astimezone().tzinfo
9
+ dt = datetime.fromtimestamp(ts, tz=locale_tz)
10
+ if isotz:
11
+ return dt.isoformat(sep=" ", timespec="seconds")
12
+ else:
13
+ return f"{datetime.strftime(dt, '%Y-%m-%d %H:%M')} {locale_tz.tzname(dt)}"
14
+ else:
15
+ return None
@@ -15,7 +15,8 @@ from modal.client import _Client
15
15
  from modal.environments import ensure_env
16
16
  from modal_proto import api_pb2
17
17
 
18
- from .utils import ENV_OPTION, display_table, get_app_id_from_name, stream_app_logs, timestamp_to_local
18
+ from .._utils.time_utils import timestamp_to_local
19
+ from .utils import ENV_OPTION, display_table, get_app_id_from_name, stream_app_logs
19
20
 
20
21
  APP_IDENTIFIER = Argument("", help="App name or ID")
21
22
  NAME_OPTION = typer.Option("", "-n", "--name", help="Deprecated: Pass App name as a positional argument")
@@ -84,6 +85,7 @@ def logs(
84
85
  app_identifier: str = APP_IDENTIFIER,
85
86
  *,
86
87
  env: Optional[str] = ENV_OPTION,
88
+ timestamps: bool = typer.Option(False, "--timestamps", help="Show timestamps for each log line"),
87
89
  ):
88
90
  """Show App logs, streaming while active.
89
91
 
@@ -103,7 +105,7 @@ def logs(
103
105
 
104
106
  """
105
107
  app_id = get_app_id(app_identifier, env)
106
- stream_app_logs(app_id)
108
+ stream_app_logs(app_id, show_timestamps=timestamps)
107
109
 
108
110
 
109
111
  @app_cli.command("rollback", no_args_is_help=True, context_settings={"ignore_unknown_options": True})
@@ -8,7 +8,8 @@ from rich.text import Text
8
8
  from modal._object import _get_environment_name
9
9
  from modal._pty import get_pty_info
10
10
  from modal._utils.async_utils import synchronizer
11
- from modal.cli.utils import ENV_OPTION, display_table, is_tty, timestamp_to_local
11
+ from modal._utils.time_utils import timestamp_to_local
12
+ from modal.cli.utils import ENV_OPTION, display_table, is_tty
12
13
  from modal.client import _Client
13
14
  from modal.config import config
14
15
  from modal.container_process import _ContainerProcess
@@ -8,7 +8,8 @@ from modal._object import _get_environment_name
8
8
  from modal._pty import get_pty_info
9
9
  from modal._utils.async_utils import synchronizer
10
10
  from modal._utils.grpc_utils import retry_transient_errors
11
- from modal.cli.utils import ENV_OPTION, display_table, is_tty, stream_app_logs, timestamp_to_local
11
+ from modal._utils.time_utils import timestamp_to_local
12
+ from modal.cli.utils import ENV_OPTION, display_table, is_tty, stream_app_logs
12
13
  from modal.client import _Client
13
14
  from modal.config import config
14
15
  from modal.container_process import _ContainerProcess
@@ -8,7 +8,8 @@ from typer import Argument, Option, Typer
8
8
  from modal._resolver import Resolver
9
9
  from modal._utils.async_utils import synchronizer
10
10
  from modal._utils.grpc_utils import retry_transient_errors
11
- from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table, timestamp_to_local
11
+ from modal._utils.time_utils import timestamp_to_local
12
+ from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
12
13
  from modal.client import _Client
13
14
  from modal.dict import _Dict
14
15
  from modal.environments import ensure_env
@@ -17,8 +17,9 @@ from modal._location import display_location
17
17
  from modal._output import OutputManager, ProgressHandler
18
18
  from modal._utils.async_utils import synchronizer
19
19
  from modal._utils.grpc_utils import retry_transient_errors
20
+ from modal._utils.time_utils import timestamp_to_local
20
21
  from modal.cli._download import _volume_download
21
- from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table, timestamp_to_local
22
+ from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
22
23
  from modal.client import _Client
23
24
  from modal.environments import ensure_env
24
25
  from modal.network_file_system import _NetworkFileSystem
@@ -8,7 +8,8 @@ from typer import Argument, Option, Typer
8
8
  from modal._resolver import Resolver
9
9
  from modal._utils.async_utils import synchronizer
10
10
  from modal._utils.grpc_utils import retry_transient_errors
11
- from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table, timestamp_to_local
11
+ from modal._utils.time_utils import timestamp_to_local
12
+ from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
12
13
  from modal.client import _Client
13
14
  from modal.environments import ensure_env
14
15
  from modal.queue import _Queue
@@ -13,7 +13,8 @@ from typer import Argument
13
13
 
14
14
  from modal._utils.async_utils import synchronizer
15
15
  from modal._utils.grpc_utils import retry_transient_errors
16
- from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table, timestamp_to_local
16
+ from modal._utils.time_utils import timestamp_to_local
17
+ from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
17
18
  from modal.client import _Client
18
19
  from modal.environments import ensure_env
19
20
  from modal.secret import _Secret
@@ -1,7 +1,6 @@
1
1
  # Copyright Modal Labs 2022
2
2
  import asyncio
3
3
  from collections.abc import Sequence
4
- from datetime import datetime
5
4
  from json import dumps
6
5
  from typing import Optional, Union
7
6
 
@@ -23,10 +22,13 @@ from ..exception import NotFoundError
23
22
 
24
23
  @synchronizer.create_blocking
25
24
  async def stream_app_logs(
26
- app_id: Optional[str] = None, task_id: Optional[str] = None, app_logs_url: Optional[str] = None
25
+ app_id: Optional[str] = None,
26
+ task_id: Optional[str] = None,
27
+ app_logs_url: Optional[str] = None,
28
+ show_timestamps: bool = False,
27
29
  ):
28
30
  client = await _Client.from_env()
29
- output_mgr = OutputManager(status_spinner_text=f"Tailing logs for {app_id}")
31
+ output_mgr = OutputManager(status_spinner_text=f"Tailing logs for {app_id}", show_timestamps=show_timestamps)
30
32
  try:
31
33
  with output_mgr.show_status_spinner():
32
34
  await get_app_logs_loop(client, output_mgr, app_id=app_id, task_id=task_id, app_logs_url=app_logs_url)
@@ -61,18 +63,6 @@ async def get_app_id_from_name(name: str, env: Optional[str], client: Optional[_
61
63
  return resp.app_id
62
64
 
63
65
 
64
- def timestamp_to_local(ts: float, isotz: bool = True) -> str:
65
- if ts > 0:
66
- locale_tz = datetime.now().astimezone().tzinfo
67
- dt = datetime.fromtimestamp(ts, tz=locale_tz)
68
- if isotz:
69
- return dt.isoformat(sep=" ", timespec="seconds")
70
- else:
71
- return f"{datetime.strftime(dt, '%Y-%m-%d %H:%M')} {locale_tz.tzname(dt)}"
72
- else:
73
- return None
74
-
75
-
76
66
  def _plain(text: Union[Text, str]) -> str:
77
67
  return text.plain if isinstance(text, Text) else text
78
68
 
@@ -15,8 +15,9 @@ import modal
15
15
  from modal._output import OutputManager, ProgressHandler
16
16
  from modal._utils.async_utils import synchronizer
17
17
  from modal._utils.grpc_utils import retry_transient_errors
18
+ from modal._utils.time_utils import timestamp_to_local
18
19
  from modal.cli._download import _volume_download
19
- from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table, timestamp_to_local
20
+ from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
20
21
  from modal.client import _Client
21
22
  from modal.environments import ensure_env
22
23
  from modal.volume import _AbstractVolumeUploadContextManager, _Volume
@@ -203,7 +204,7 @@ async def put(
203
204
  vol.object_id,
204
205
  vol._client,
205
206
  progress_cb=progress_handler.progress,
206
- force=force
207
+ force=force,
207
208
  ) as batch:
208
209
  batch.put_directory(local_path, remote_path)
209
210
  except FileExistsError as exc:
@@ -219,7 +220,7 @@ async def put(
219
220
  vol.object_id,
220
221
  vol._client,
221
222
  progress_cb=progress_handler.progress,
222
- force=force
223
+ force=force,
223
224
  ) as batch:
224
225
  batch.put_file(local_path, remote_path)
225
226
 
@@ -3,6 +3,7 @@ import asyncio
3
3
  import os
4
4
  import platform
5
5
  import sys
6
+ import urllib.parse
6
7
  import warnings
7
8
  from collections.abc import AsyncGenerator, AsyncIterator, Collection, Mapping
8
9
  from typing import (
@@ -50,8 +51,8 @@ def _get_metadata(client_type: int, credentials: Optional[tuple[str, str]], vers
50
51
  "x-modal-client-version": version,
51
52
  "x-modal-client-type": str(client_type),
52
53
  "x-modal-python-version": python_version,
53
- "x-modal-node": platform.node(),
54
- "x-modal-platform": platform_str,
54
+ "x-modal-node": urllib.parse.quote(platform.node()),
55
+ "x-modal-platform": urllib.parse.quote(platform_str),
55
56
  }
56
57
  if credentials and client_type == api_pb2.CLIENT_TYPE_CLIENT:
57
58
  token_id, token_secret = credentials
@@ -31,7 +31,7 @@ class _Client:
31
31
  server_url: str,
32
32
  client_type: int,
33
33
  credentials: typing.Optional[tuple[str, str]],
34
- version: str = "1.0.1.dev3",
34
+ version: str = "1.0.1.dev5",
35
35
  ): ...
36
36
  def is_closed(self) -> bool: ...
37
37
  @property
@@ -94,7 +94,7 @@ class Client:
94
94
  server_url: str,
95
95
  client_type: int,
96
96
  credentials: typing.Optional[tuple[str, str]],
97
- version: str = "1.0.1.dev3",
97
+ version: str = "1.0.1.dev5",
98
98
  ): ...
99
99
  def is_closed(self) -> bool: ...
100
100
  @property
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.0.1.dev3
3
+ Version: 1.0.1.dev5
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -119,6 +119,7 @@ modal/_utils/package_utils.py
119
119
  modal/_utils/pattern_utils.py
120
120
  modal/_utils/rand_pb_testing.py
121
121
  modal/_utils/shell_utils.py
122
+ modal/_utils/time_utils.py
122
123
  modal/_vendor/__init__.py
123
124
  modal/_vendor/a2wsgi_wsgi.py
124
125
  modal/_vendor/cloudpickle.py
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.0.1.dev3"
4
+ __version__ = "1.0.1.dev5"
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
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
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
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