modal 1.1.1.dev38__tar.gz → 1.1.1.dev40__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.

Potentially problematic release.


This version of modal might be problematic. Click here for more details.

Files changed (189) hide show
  1. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/PKG-INFO +1 -1
  2. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_functions.py +1 -2
  3. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_output.py +2 -2
  4. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/async_utils.py +6 -4
  5. modal-1.1.1.dev40/modal/_utils/time_utils.py +19 -0
  6. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/app.py +4 -4
  7. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/cluster.py +2 -2
  8. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/container.py +2 -2
  9. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/dict.py +2 -2
  10. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/network_file_system.py +2 -2
  11. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/queues.py +2 -2
  12. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/secret.py +3 -3
  13. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/volume.py +3 -3
  14. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/client.pyi +2 -2
  15. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/dict.py +2 -1
  16. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/parallel_map.py +224 -85
  17. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/parallel_map.pyi +12 -5
  18. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/queue.py +2 -1
  19. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/secret.py +2 -1
  20. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/volume.py +2 -1
  21. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal.egg-info/PKG-INFO +1 -1
  22. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_version/__init__.py +1 -1
  23. modal-1.1.1.dev38/modal/_utils/time_utils.py +0 -15
  24. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/LICENSE +0 -0
  25. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/README.md +0 -0
  26. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/__init__.py +0 -0
  27. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/__main__.py +0 -0
  28. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_clustered_functions.py +0 -0
  29. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_clustered_functions.pyi +0 -0
  30. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_container_entrypoint.py +0 -0
  31. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_ipython.py +0 -0
  32. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_location.py +0 -0
  33. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_object.py +0 -0
  34. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_partial_function.py +0 -0
  35. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_pty.py +0 -0
  36. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_resolver.py +0 -0
  37. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_resources.py +0 -0
  38. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_runtime/__init__.py +0 -0
  39. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_runtime/asgi.py +0 -0
  40. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_runtime/container_io_manager.py +0 -0
  41. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_runtime/container_io_manager.pyi +0 -0
  42. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_runtime/execution_context.py +0 -0
  43. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_runtime/execution_context.pyi +0 -0
  44. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  45. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_runtime/telemetry.py +0 -0
  46. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_runtime/user_code_imports.py +0 -0
  47. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_serialization.py +0 -0
  48. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_traceback.py +0 -0
  49. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_tunnel.py +0 -0
  50. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_tunnel.pyi +0 -0
  51. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_type_manager.py +0 -0
  52. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/__init__.py +0 -0
  53. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/app_utils.py +0 -0
  54. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/auth_token_manager.py +0 -0
  55. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/blob_utils.py +0 -0
  56. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/bytes_io_segment_payload.py +0 -0
  57. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/deprecation.py +0 -0
  58. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/docker_utils.py +0 -0
  59. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/function_utils.py +0 -0
  60. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/git_utils.py +0 -0
  61. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/grpc_testing.py +0 -0
  62. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/grpc_utils.py +0 -0
  63. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/hash_utils.py +0 -0
  64. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/http_utils.py +0 -0
  65. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/jwt_utils.py +0 -0
  66. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/logger.py +0 -0
  67. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/mount_utils.py +0 -0
  68. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/name_utils.py +0 -0
  69. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/package_utils.py +0 -0
  70. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/pattern_utils.py +0 -0
  71. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/rand_pb_testing.py +0 -0
  72. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_utils/shell_utils.py +0 -0
  73. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_vendor/__init__.py +0 -0
  74. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  75. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_vendor/cloudpickle.py +0 -0
  76. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_vendor/tblib.py +0 -0
  77. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/_watcher.py +0 -0
  78. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/app.py +0 -0
  79. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/app.pyi +0 -0
  80. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/builder/2023.12.312.txt +0 -0
  81. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/builder/2023.12.txt +0 -0
  82. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/builder/2024.04.txt +0 -0
  83. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/builder/2024.10.txt +0 -0
  84. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/builder/2025.06.txt +0 -0
  85. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/builder/PREVIEW.txt +0 -0
  86. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/builder/README.md +0 -0
  87. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/builder/base-images.json +0 -0
  88. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/call_graph.py +0 -0
  89. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/__init__.py +0 -0
  90. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/_download.py +0 -0
  91. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/_traceback.py +0 -0
  92. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/config.py +0 -0
  93. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/entry_point.py +0 -0
  94. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/environment.py +0 -0
  95. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/import_refs.py +0 -0
  96. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/launch.py +0 -0
  97. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/profile.py +0 -0
  98. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/programs/__init__.py +0 -0
  99. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/programs/run_jupyter.py +0 -0
  100. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/programs/vscode.py +0 -0
  101. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/run.py +0 -0
  102. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/token.py +0 -0
  103. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cli/utils.py +0 -0
  104. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/client.py +0 -0
  105. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cloud_bucket_mount.py +0 -0
  106. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cloud_bucket_mount.pyi +0 -0
  107. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cls.py +0 -0
  108. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/cls.pyi +0 -0
  109. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/config.py +0 -0
  110. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/container_process.py +0 -0
  111. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/container_process.pyi +0 -0
  112. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/dict.pyi +0 -0
  113. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/environments.py +0 -0
  114. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/environments.pyi +0 -0
  115. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/exception.py +0 -0
  116. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/experimental/__init__.py +0 -0
  117. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/experimental/flash.py +0 -0
  118. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/experimental/flash.pyi +0 -0
  119. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/experimental/ipython.py +0 -0
  120. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/file_io.py +0 -0
  121. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/file_io.pyi +0 -0
  122. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/file_pattern_matcher.py +0 -0
  123. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/functions.py +0 -0
  124. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/functions.pyi +0 -0
  125. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/gpu.py +0 -0
  126. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/image.py +0 -0
  127. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/image.pyi +0 -0
  128. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/io_streams.py +0 -0
  129. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/io_streams.pyi +0 -0
  130. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/mount.py +0 -0
  131. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/mount.pyi +0 -0
  132. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/network_file_system.py +0 -0
  133. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/network_file_system.pyi +0 -0
  134. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/object.py +0 -0
  135. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/object.pyi +0 -0
  136. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/output.py +0 -0
  137. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/partial_function.py +0 -0
  138. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/partial_function.pyi +0 -0
  139. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/proxy.py +0 -0
  140. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/proxy.pyi +0 -0
  141. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/py.typed +0 -0
  142. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/queue.pyi +0 -0
  143. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/retries.py +0 -0
  144. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/runner.py +0 -0
  145. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/runner.pyi +0 -0
  146. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/running_app.py +0 -0
  147. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/sandbox.py +0 -0
  148. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/sandbox.pyi +0 -0
  149. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/schedule.py +0 -0
  150. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/scheduler_placement.py +0 -0
  151. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/secret.pyi +0 -0
  152. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/serving.py +0 -0
  153. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/serving.pyi +0 -0
  154. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/snapshot.py +0 -0
  155. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/snapshot.pyi +0 -0
  156. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/stream_type.py +0 -0
  157. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/token_flow.py +0 -0
  158. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/token_flow.pyi +0 -0
  159. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal/volume.pyi +0 -0
  160. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal.egg-info/SOURCES.txt +0 -0
  161. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal.egg-info/dependency_links.txt +0 -0
  162. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal.egg-info/entry_points.txt +0 -0
  163. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal.egg-info/requires.txt +0 -0
  164. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal.egg-info/top_level.txt +0 -0
  165. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_docs/__init__.py +0 -0
  166. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_docs/gen_cli_docs.py +0 -0
  167. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_docs/gen_reference_docs.py +0 -0
  168. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_docs/mdmd/__init__.py +0 -0
  169. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_docs/mdmd/mdmd.py +0 -0
  170. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_docs/mdmd/signatures.py +0 -0
  171. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/__init__.py +0 -0
  172. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/api.proto +0 -0
  173. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/api_grpc.py +0 -0
  174. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/api_pb2.py +0 -0
  175. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/api_pb2.pyi +0 -0
  176. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/api_pb2_grpc.py +0 -0
  177. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/api_pb2_grpc.pyi +0 -0
  178. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/modal_api_grpc.py +0 -0
  179. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/modal_options_grpc.py +0 -0
  180. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/options.proto +0 -0
  181. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/options_grpc.py +0 -0
  182. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/options_pb2.py +0 -0
  183. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/options_pb2.pyi +0 -0
  184. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/options_pb2_grpc.py +0 -0
  185. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/options_pb2_grpc.pyi +0 -0
  186. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_proto/py.typed +0 -0
  187. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/modal_version/__main__.py +0 -0
  188. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/pyproject.toml +0 -0
  189. {modal-1.1.1.dev38 → modal-1.1.1.dev40}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.1.dev38
3
+ Version: 1.1.1.dev40
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -1513,8 +1513,7 @@ Use the `Function.get_web_url()` method instead.
1513
1513
  else:
1514
1514
  count_update_callback = None
1515
1515
 
1516
- # TODO(ben-okeefe): Feature gating for input plane map until feature is enabled.
1517
- if self._input_plane_url and False:
1516
+ if self._input_plane_url:
1518
1517
  async with aclosing(
1519
1518
  _map_invocation_inputplane(
1520
1519
  self,
@@ -31,7 +31,7 @@ from rich.progress import (
31
31
  from rich.spinner import Spinner
32
32
  from rich.text import Text
33
33
 
34
- from modal._utils.time_utils import timestamp_to_local
34
+ from modal._utils.time_utils import timestamp_to_localized_str
35
35
  from modal_proto import api_pb2
36
36
 
37
37
  from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, retry_transient_errors
@@ -91,7 +91,7 @@ class LineBufferedOutput:
91
91
 
92
92
  if self._show_timestamps:
93
93
  for i in range(0, len(chunks) - 1, 2):
94
- chunks[i] = f"{timestamp_to_local(log.timestamp)} {chunks[i]}"
94
+ chunks[i] = f"{timestamp_to_localized_str(log.timestamp)} {chunks[i]}"
95
95
 
96
96
  completed_lines = "".join(chunks[:-1])
97
97
  remainder = chunks[-1]
@@ -279,7 +279,9 @@ class TimestampPriorityQueue(Generic[T]):
279
279
 
280
280
  def __init__(self, maxsize: int = 0):
281
281
  self.condition = asyncio.Condition()
282
- self._queue: asyncio.PriorityQueue[tuple[float, Union[T, None]]] = asyncio.PriorityQueue(maxsize=maxsize)
282
+ self._queue: asyncio.PriorityQueue[tuple[float, int, Union[T, None]]] = asyncio.PriorityQueue(maxsize=maxsize)
283
+ # Used to tiebreak items with the same timestamp that are not comparable. (eg. protos)
284
+ self._counter = itertools.count()
283
285
 
284
286
  async def close(self):
285
287
  await self.put(self._MAX_PRIORITY, None)
@@ -288,7 +290,7 @@ class TimestampPriorityQueue(Generic[T]):
288
290
  """
289
291
  Add an item to the queue to be processed at a specific timestamp.
290
292
  """
291
- await self._queue.put((timestamp, item))
293
+ await self._queue.put((timestamp, next(self._counter), item))
292
294
  async with self.condition:
293
295
  self.condition.notify_all() # notify any waiting coroutines
294
296
 
@@ -301,7 +303,7 @@ class TimestampPriorityQueue(Generic[T]):
301
303
  while self.empty():
302
304
  await self.condition.wait()
303
305
  # peek at the next item
304
- timestamp, item = await self._queue.get()
306
+ timestamp, counter, item = await self._queue.get()
305
307
  now = time.time()
306
308
  if timestamp < now:
307
309
  return item
@@ -309,7 +311,7 @@ class TimestampPriorityQueue(Generic[T]):
309
311
  return None
310
312
  # not ready yet, calculate sleep time
311
313
  sleep_time = timestamp - now
312
- self._queue.put_nowait((timestamp, item)) # put it back
314
+ self._queue.put_nowait((timestamp, counter, item)) # put it back
313
315
  # wait until either the timeout or a new item is added
314
316
  try:
315
317
  await asyncio.wait_for(self.condition.wait(), timeout=sleep_time)
@@ -0,0 +1,19 @@
1
+ # Copyright Modal Labs 2025
2
+ from datetime import datetime
3
+ from typing import Optional
4
+
5
+
6
+ def timestamp_to_localized_dt(ts: float) -> datetime:
7
+ locale_tz = datetime.now().astimezone().tzinfo
8
+ return datetime.fromtimestamp(ts, tz=locale_tz)
9
+
10
+
11
+ def timestamp_to_localized_str(ts: float, isotz: bool = True) -> Optional[str]:
12
+ if ts > 0:
13
+ dt = timestamp_to_localized_dt(ts)
14
+ if isotz:
15
+ return dt.isoformat(sep=" ", timespec="seconds")
16
+ else:
17
+ return f"{dt:%Y-%m-%d %H:%M %Z}"
18
+ else:
19
+ return None
@@ -15,7 +15,7 @@ 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.time_utils import timestamp_to_local
18
+ from .._utils.time_utils import timestamp_to_localized_str
19
19
  from .utils import ENV_OPTION, display_table, get_app_id_from_name, stream_app_logs
20
20
 
21
21
  APP_IDENTIFIER = Argument("", help="App name or ID")
@@ -71,8 +71,8 @@ async def list_(env: Optional[str] = ENV_OPTION, json: bool = False):
71
71
  app_stats.description,
72
72
  state,
73
73
  str(app_stats.n_running_tasks),
74
- timestamp_to_local(app_stats.created_at, json),
75
- timestamp_to_local(app_stats.stopped_at, json),
74
+ timestamp_to_localized_str(app_stats.created_at, json),
75
+ timestamp_to_localized_str(app_stats.stopped_at, json),
76
76
  ]
77
77
  )
78
78
 
@@ -217,7 +217,7 @@ async def history(
217
217
 
218
218
  row = [
219
219
  Text(f"v{app_stats.version}", style=style),
220
- Text(timestamp_to_local(app_stats.deployed_at, json), style=style),
220
+ Text(timestamp_to_localized_str(app_stats.deployed_at, json), style=style),
221
221
  Text(app_stats.client_version, style=style),
222
222
  Text(app_stats.deployed_by, style=style),
223
223
  ]
@@ -8,7 +8,7 @@ from modal._object import _get_environment_name
8
8
  from modal._output import make_console
9
9
  from modal._pty import get_pty_info
10
10
  from modal._utils.async_utils import synchronizer
11
- from modal._utils.time_utils import timestamp_to_local
11
+ from modal._utils.time_utils import timestamp_to_localized_str
12
12
  from modal.cli.utils import ENV_OPTION, display_table, is_tty
13
13
  from modal.client import _Client
14
14
  from modal.config import config
@@ -42,7 +42,7 @@ async def list_(env: Optional[str] = ENV_OPTION, json: bool = False):
42
42
  [
43
43
  c.cluster_id,
44
44
  c.app_id,
45
- timestamp_to_local(c.started_at, json) if c.started_at else "Pending",
45
+ timestamp_to_localized_str(c.started_at, json) if c.started_at else "Pending",
46
46
  str(len(c.task_ids)),
47
47
  ]
48
48
  )
@@ -8,7 +8,7 @@ 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._utils.time_utils import timestamp_to_local
11
+ from modal._utils.time_utils import timestamp_to_localized_str
12
12
  from modal.cli.utils import ENV_OPTION, display_table, is_tty, stream_app_logs
13
13
  from modal.client import _Client
14
14
  from modal.config import config
@@ -40,7 +40,7 @@ async def list_(env: Optional[str] = ENV_OPTION, json: bool = False):
40
40
  task_stats.task_id,
41
41
  task_stats.app_id,
42
42
  task_stats.app_description,
43
- timestamp_to_local(task_stats.started_at, json) if task_stats.started_at else "Pending",
43
+ timestamp_to_localized_str(task_stats.started_at, json) if task_stats.started_at else "Pending",
44
44
  ]
45
45
  )
46
46
 
@@ -8,7 +8,7 @@ from modal._output import make_console
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._utils.time_utils import timestamp_to_local
11
+ from modal._utils.time_utils import timestamp_to_localized_str
12
12
  from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
13
13
  from modal.client import _Client
14
14
  from modal.dict import _Dict
@@ -44,7 +44,7 @@ async def list_(*, json: bool = False, env: Optional[str] = ENV_OPTION):
44
44
  request = api_pb2.DictListRequest(environment_name=env)
45
45
  response = await retry_transient_errors(client.stub.DictList, request)
46
46
 
47
- rows = [(d.name, timestamp_to_local(d.created_at, json)) for d in response.dicts]
47
+ rows = [(d.name, timestamp_to_localized_str(d.created_at, json)) for d in response.dicts]
48
48
  display_table(["Name", "Created at"], rows, json)
49
49
 
50
50
 
@@ -16,7 +16,7 @@ from modal._location import display_location
16
16
  from modal._output import OutputManager, ProgressHandler, make_console
17
17
  from modal._utils.async_utils import synchronizer
18
18
  from modal._utils.grpc_utils import retry_transient_errors
19
- from modal._utils.time_utils import timestamp_to_local
19
+ from modal._utils.time_utils import timestamp_to_localized_str
20
20
  from modal.cli._download import _volume_download
21
21
  from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
22
22
  from modal.client import _Client
@@ -44,7 +44,7 @@ async def list_(env: Optional[str] = ENV_OPTION, json: Optional[bool] = False):
44
44
  [
45
45
  item.label,
46
46
  display_location(item.cloud_provider),
47
- timestamp_to_local(item.created_at, json),
47
+ timestamp_to_localized_str(item.created_at, json),
48
48
  ]
49
49
  )
50
50
  display_table(column_names, rows, json, title=f"Shared Volumes{env_part}")
@@ -8,7 +8,7 @@ from modal._output import make_console
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._utils.time_utils import timestamp_to_local
11
+ from modal._utils.time_utils import timestamp_to_localized_str
12
12
  from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
13
13
  from modal.client import _Client
14
14
  from modal.environments import ensure_env
@@ -71,7 +71,7 @@ async def list_(*, json: bool = False, env: Optional[str] = ENV_OPTION):
71
71
  rows = [
72
72
  (
73
73
  q.name,
74
- timestamp_to_local(q.created_at, json),
74
+ timestamp_to_localized_str(q.created_at, json),
75
75
  str(q.num_partitions),
76
76
  str(q.total_size) if q.total_size <= max_total_size else f">{max_total_size}",
77
77
  )
@@ -15,7 +15,7 @@ from typer import Argument
15
15
  from modal._output import make_console
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
+ from modal._utils.time_utils import timestamp_to_localized_str
19
19
  from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
20
20
  from modal.client import _Client
21
21
  from modal.environments import ensure_env
@@ -38,8 +38,8 @@ async def list_(env: Optional[str] = ENV_OPTION, json: bool = False):
38
38
  rows.append(
39
39
  [
40
40
  item.label,
41
- timestamp_to_local(item.created_at, json),
42
- timestamp_to_local(item.last_used_at, json) if item.last_used_at else "-",
41
+ timestamp_to_localized_str(item.created_at, json),
42
+ timestamp_to_localized_str(item.last_used_at, json) if item.last_used_at else "-",
43
43
  ]
44
44
  )
45
45
 
@@ -14,7 +14,7 @@ import modal
14
14
  from modal._output import OutputManager, ProgressHandler, make_console
15
15
  from modal._utils.async_utils import synchronizer
16
16
  from modal._utils.grpc_utils import retry_transient_errors
17
- from modal._utils.time_utils import timestamp_to_local
17
+ from modal._utils.time_utils import timestamp_to_localized_str
18
18
  from modal.cli._download import _volume_download
19
19
  from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
20
20
  from modal.client import _Client
@@ -116,7 +116,7 @@ async def list_(env: Optional[str] = ENV_OPTION, json: Optional[bool] = False):
116
116
  column_names = ["Name", "Created at"]
117
117
  rows = []
118
118
  for item in response.items:
119
- rows.append([item.label, timestamp_to_local(item.created_at, json)])
119
+ rows.append([item.label, timestamp_to_localized_str(item.created_at, json)])
120
120
  display_table(column_names, rows, json, title=f"Volumes{env_part}")
121
121
 
122
122
 
@@ -163,7 +163,7 @@ async def ls(
163
163
  (
164
164
  entry.path.encode("unicode_escape").decode("utf-8"),
165
165
  filetype,
166
- timestamp_to_local(entry.mtime, False),
166
+ timestamp_to_localized_str(entry.mtime, False),
167
167
  humanize_filesize(entry.size),
168
168
  )
169
169
  )
@@ -33,7 +33,7 @@ class _Client:
33
33
  server_url: str,
34
34
  client_type: int,
35
35
  credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.1.1.dev38",
36
+ version: str = "1.1.1.dev40",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -164,7 +164,7 @@ class Client:
164
164
  server_url: str,
165
165
  client_type: int,
166
166
  credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.1.1.dev38",
167
+ version: str = "1.1.1.dev40",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
@@ -17,6 +17,7 @@ from ._utils.async_utils import TaskContext, synchronize_api
17
17
  from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
18
18
  from ._utils.grpc_utils import retry_transient_errors
19
19
  from ._utils.name_utils import check_object_name
20
+ from ._utils.time_utils import timestamp_to_localized_dt
20
21
  from .client import _Client
21
22
  from .config import logger
22
23
  from .exception import RequestSizeError
@@ -248,7 +249,7 @@ class _Dict(_Object, type_prefix="di"):
248
249
  creation_info = metadata.creation_info
249
250
  return DictInfo(
250
251
  name=metadata.name or None,
251
- created_at=datetime.fromtimestamp(creation_info.created_at),
252
+ created_at=timestamp_to_localized_dt(creation_info.created_at),
252
253
  created_by=creation_info.created_by or None,
253
254
  )
254
255