modAL 1.3.1__tar.gz → 1.3.1.dev0__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 (198) hide show
  1. {modal-1.3.1 → modal-1.3.1.dev0}/PKG-INFO +2 -3
  2. {modal-1.3.1 → modal-1.3.1.dev0}/modal/__init__.py +0 -2
  3. {modal-1.3.1 → modal-1.3.1.dev0}/modal/__main__.py +1 -2
  4. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_billing.py +5 -9
  5. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_container_entrypoint.py +1 -13
  6. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_functions.py +2 -5
  7. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_grpc_client.py +3 -3
  8. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_load_context.py +2 -26
  9. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_object.py +11 -16
  10. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_output.py +2 -2
  11. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_partial_function.py +22 -1
  12. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_resolver.py +31 -34
  13. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_runtime/user_code_imports.py +63 -131
  14. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_traceback.py +12 -3
  15. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/async_utils.py +22 -51
  16. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/blob_utils.py +9 -3
  17. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/function_utils.py +4 -3
  18. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/grpc_utils.py +9 -7
  19. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/task_command_router_client.py +38 -25
  20. {modal-1.3.1 → modal-1.3.1.dev0}/modal/app.py +14 -159
  21. {modal-1.3.1 → modal-1.3.1.dev0}/modal/app.pyi +3 -121
  22. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/_traceback.py +1 -2
  23. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/container.py +2 -5
  24. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/dict.py +3 -4
  25. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/import_refs.py +2 -2
  26. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/queues.py +3 -4
  27. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/run.py +13 -65
  28. modal-1.3.1.dev0/modal/cli/token.py +60 -0
  29. {modal-1.3.1 → modal-1.3.1.dev0}/modal/client.py +0 -2
  30. {modal-1.3.1 → modal-1.3.1.dev0}/modal/client.pyi +10 -5
  31. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cls.py +3 -0
  32. {modal-1.3.1 → modal-1.3.1.dev0}/modal/experimental/__init__.py +1 -1
  33. {modal-1.3.1 → modal-1.3.1.dev0}/modal/experimental/flash.py +7 -3
  34. {modal-1.3.1 → modal-1.3.1.dev0}/modal/functions.pyi +6 -7
  35. {modal-1.3.1 → modal-1.3.1.dev0}/modal/image.py +12 -37
  36. {modal-1.3.1 → modal-1.3.1.dev0}/modal/image.pyi +0 -4
  37. {modal-1.3.1 → modal-1.3.1.dev0}/modal/mount.py +4 -10
  38. {modal-1.3.1 → modal-1.3.1.dev0}/modal/mount.pyi +3 -3
  39. {modal-1.3.1 → modal-1.3.1.dev0}/modal/output.py +2 -2
  40. {modal-1.3.1 → modal-1.3.1.dev0}/modal/partial_function.pyi +1 -0
  41. {modal-1.3.1 → modal-1.3.1.dev0}/modal/runner.py +12 -29
  42. {modal-1.3.1 → modal-1.3.1.dev0}/modal/runner.pyi +1 -5
  43. {modal-1.3.1 → modal-1.3.1.dev0}/modal/sandbox.py +2 -12
  44. {modal-1.3.1 → modal-1.3.1.dev0}/modal/sandbox.pyi +0 -8
  45. {modal-1.3.1 → modal-1.3.1.dev0}/modal/volume.pyi +2 -2
  46. {modal-1.3.1 → modal-1.3.1.dev0}/modal.egg-info/PKG-INFO +2 -3
  47. {modal-1.3.1 → modal-1.3.1.dev0}/modal.egg-info/SOURCES.txt +0 -3
  48. {modal-1.3.1 → modal-1.3.1.dev0}/modal.egg-info/requires.txt +1 -6
  49. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/api.proto +2 -56
  50. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/api_grpc.py +0 -16
  51. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/api_pb2.py +656 -717
  52. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/api_pb2.pyi +6 -178
  53. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/api_pb2_grpc.py +0 -33
  54. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/api_pb2_grpc.pyi +0 -10
  55. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/modal_api_grpc.py +0 -1
  56. {modal-1.3.1 → modal-1.3.1.dev0}/modal_version/__init__.py +2 -2
  57. {modal-1.3.1 → modal-1.3.1.dev0}/pyproject.toml +1 -2
  58. modal-1.3.1/modal/_server.py +0 -252
  59. modal-1.3.1/modal/cli/token.py +0 -108
  60. modal-1.3.1/modal/server.py +0 -5
  61. modal-1.3.1/modal/server.pyi +0 -147
  62. {modal-1.3.1 → modal-1.3.1.dev0}/LICENSE +0 -0
  63. {modal-1.3.1 → modal-1.3.1.dev0}/README.md +0 -0
  64. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_clustered_functions.py +0 -0
  65. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_clustered_functions.pyi +0 -0
  66. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_ipython.py +0 -0
  67. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_location.py +0 -0
  68. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_pty.py +0 -0
  69. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_resources.py +0 -0
  70. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_runtime/__init__.py +0 -0
  71. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_runtime/asgi.py +0 -0
  72. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_runtime/container_io_manager.py +0 -0
  73. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
  74. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_runtime/execution_context.py +0 -0
  75. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_runtime/execution_context.pyi +0 -0
  76. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  77. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_runtime/telemetry.py +0 -0
  78. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
  79. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_serialization.py +0 -0
  80. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_tunnel.py +0 -0
  81. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_tunnel.pyi +0 -0
  82. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_type_manager.py +0 -0
  83. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/__init__.py +0 -0
  84. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/app_utils.py +0 -0
  85. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/auth_token_manager.py +0 -0
  86. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
  87. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/deprecation.py +0 -0
  88. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/docker_utils.py +0 -0
  89. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/git_utils.py +0 -0
  90. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/grpc_testing.py +0 -0
  91. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/hash_utils.py +0 -0
  92. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/http_utils.py +0 -0
  93. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/jwt_utils.py +0 -0
  94. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/logger.py +0 -0
  95. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/mount_utils.py +0 -0
  96. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/name_utils.py +0 -0
  97. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/package_utils.py +0 -0
  98. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/pattern_utils.py +0 -0
  99. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/rand_pb_testing.py +0 -0
  100. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/shell_utils.py +0 -0
  101. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_utils/time_utils.py +0 -0
  102. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_vendor/__init__.py +0 -0
  103. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  104. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_vendor/cloudpickle.py +0 -0
  105. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_vendor/tblib.py +0 -0
  106. {modal-1.3.1 → modal-1.3.1.dev0}/modal/_watcher.py +0 -0
  107. {modal-1.3.1 → modal-1.3.1.dev0}/modal/billing.py +0 -0
  108. {modal-1.3.1 → modal-1.3.1.dev0}/modal/builder/2023.12.312.txt +0 -0
  109. {modal-1.3.1 → modal-1.3.1.dev0}/modal/builder/2023.12.txt +0 -0
  110. {modal-1.3.1 → modal-1.3.1.dev0}/modal/builder/2024.04.txt +0 -0
  111. {modal-1.3.1 → modal-1.3.1.dev0}/modal/builder/2024.10.txt +0 -0
  112. {modal-1.3.1 → modal-1.3.1.dev0}/modal/builder/2025.06.txt +0 -0
  113. {modal-1.3.1 → modal-1.3.1.dev0}/modal/builder/PREVIEW.txt +0 -0
  114. {modal-1.3.1 → modal-1.3.1.dev0}/modal/builder/README.md +0 -0
  115. {modal-1.3.1 → modal-1.3.1.dev0}/modal/builder/base-images.json +0 -0
  116. {modal-1.3.1 → modal-1.3.1.dev0}/modal/call_graph.py +0 -0
  117. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/__init__.py +0 -0
  118. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/_download.py +0 -0
  119. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/app.py +0 -0
  120. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/cluster.py +0 -0
  121. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/config.py +0 -0
  122. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/entry_point.py +0 -0
  123. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/environment.py +0 -0
  124. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/launch.py +0 -0
  125. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/network_file_system.py +0 -0
  126. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/profile.py +0 -0
  127. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/programs/__init__.py +0 -0
  128. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/programs/run_jupyter.py +0 -0
  129. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/programs/vscode.py +0 -0
  130. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/secret.py +0 -0
  131. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/shell.py +0 -0
  132. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/utils.py +0 -0
  133. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cli/volume.py +0 -0
  134. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cloud_bucket_mount.py +0 -0
  135. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cloud_bucket_mount.pyi +0 -0
  136. {modal-1.3.1 → modal-1.3.1.dev0}/modal/cls.pyi +0 -0
  137. {modal-1.3.1 → modal-1.3.1.dev0}/modal/config.py +0 -0
  138. {modal-1.3.1 → modal-1.3.1.dev0}/modal/container_process.py +0 -0
  139. {modal-1.3.1 → modal-1.3.1.dev0}/modal/container_process.pyi +0 -0
  140. {modal-1.3.1 → modal-1.3.1.dev0}/modal/dict.py +0 -0
  141. {modal-1.3.1 → modal-1.3.1.dev0}/modal/dict.pyi +0 -0
  142. {modal-1.3.1 → modal-1.3.1.dev0}/modal/environments.py +0 -0
  143. {modal-1.3.1 → modal-1.3.1.dev0}/modal/environments.pyi +0 -0
  144. {modal-1.3.1 → modal-1.3.1.dev0}/modal/exception.py +0 -0
  145. {modal-1.3.1 → modal-1.3.1.dev0}/modal/experimental/flash.pyi +0 -0
  146. {modal-1.3.1 → modal-1.3.1.dev0}/modal/experimental/ipython.py +0 -0
  147. {modal-1.3.1 → modal-1.3.1.dev0}/modal/file_io.py +0 -0
  148. {modal-1.3.1 → modal-1.3.1.dev0}/modal/file_io.pyi +0 -0
  149. {modal-1.3.1 → modal-1.3.1.dev0}/modal/file_pattern_matcher.py +0 -0
  150. {modal-1.3.1 → modal-1.3.1.dev0}/modal/functions.py +0 -0
  151. {modal-1.3.1 → modal-1.3.1.dev0}/modal/gpu.py +0 -0
  152. {modal-1.3.1 → modal-1.3.1.dev0}/modal/io_streams.py +0 -0
  153. {modal-1.3.1 → modal-1.3.1.dev0}/modal/io_streams.pyi +0 -0
  154. {modal-1.3.1 → modal-1.3.1.dev0}/modal/network_file_system.py +0 -0
  155. {modal-1.3.1 → modal-1.3.1.dev0}/modal/network_file_system.pyi +0 -0
  156. {modal-1.3.1 → modal-1.3.1.dev0}/modal/object.py +0 -0
  157. {modal-1.3.1 → modal-1.3.1.dev0}/modal/object.pyi +0 -0
  158. {modal-1.3.1 → modal-1.3.1.dev0}/modal/parallel_map.py +0 -0
  159. {modal-1.3.1 → modal-1.3.1.dev0}/modal/parallel_map.pyi +0 -0
  160. {modal-1.3.1 → modal-1.3.1.dev0}/modal/partial_function.py +0 -0
  161. {modal-1.3.1 → modal-1.3.1.dev0}/modal/proxy.py +0 -0
  162. {modal-1.3.1 → modal-1.3.1.dev0}/modal/proxy.pyi +0 -0
  163. {modal-1.3.1 → modal-1.3.1.dev0}/modal/py.typed +0 -0
  164. {modal-1.3.1 → modal-1.3.1.dev0}/modal/queue.py +0 -0
  165. {modal-1.3.1 → modal-1.3.1.dev0}/modal/queue.pyi +0 -0
  166. {modal-1.3.1 → modal-1.3.1.dev0}/modal/retries.py +0 -0
  167. {modal-1.3.1 → modal-1.3.1.dev0}/modal/running_app.py +0 -0
  168. {modal-1.3.1 → modal-1.3.1.dev0}/modal/schedule.py +0 -0
  169. {modal-1.3.1 → modal-1.3.1.dev0}/modal/scheduler_placement.py +0 -0
  170. {modal-1.3.1 → modal-1.3.1.dev0}/modal/secret.py +0 -0
  171. {modal-1.3.1 → modal-1.3.1.dev0}/modal/secret.pyi +0 -0
  172. {modal-1.3.1 → modal-1.3.1.dev0}/modal/serving.py +0 -0
  173. {modal-1.3.1 → modal-1.3.1.dev0}/modal/serving.pyi +0 -0
  174. {modal-1.3.1 → modal-1.3.1.dev0}/modal/snapshot.py +0 -0
  175. {modal-1.3.1 → modal-1.3.1.dev0}/modal/snapshot.pyi +0 -0
  176. {modal-1.3.1 → modal-1.3.1.dev0}/modal/stream_type.py +0 -0
  177. {modal-1.3.1 → modal-1.3.1.dev0}/modal/token_flow.py +0 -0
  178. {modal-1.3.1 → modal-1.3.1.dev0}/modal/token_flow.pyi +0 -0
  179. {modal-1.3.1 → modal-1.3.1.dev0}/modal/volume.py +0 -0
  180. {modal-1.3.1 → modal-1.3.1.dev0}/modal.egg-info/dependency_links.txt +0 -0
  181. {modal-1.3.1 → modal-1.3.1.dev0}/modal.egg-info/entry_points.txt +0 -0
  182. {modal-1.3.1 → modal-1.3.1.dev0}/modal.egg-info/top_level.txt +0 -0
  183. {modal-1.3.1 → modal-1.3.1.dev0}/modal_docs/__init__.py +0 -0
  184. {modal-1.3.1 → modal-1.3.1.dev0}/modal_docs/gen_cli_docs.py +0 -0
  185. {modal-1.3.1 → modal-1.3.1.dev0}/modal_docs/gen_reference_docs.py +0 -0
  186. {modal-1.3.1 → modal-1.3.1.dev0}/modal_docs/mdmd/__init__.py +0 -0
  187. {modal-1.3.1 → modal-1.3.1.dev0}/modal_docs/mdmd/mdmd.py +0 -0
  188. {modal-1.3.1 → modal-1.3.1.dev0}/modal_docs/mdmd/signatures.py +0 -0
  189. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/__init__.py +0 -0
  190. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/py.typed +0 -0
  191. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/task_command_router.proto +0 -0
  192. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/task_command_router_grpc.py +0 -0
  193. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/task_command_router_pb2.py +0 -0
  194. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
  195. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  196. {modal-1.3.1 → modal-1.3.1.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  197. {modal-1.3.1 → modal-1.3.1.dev0}/modal_version/__main__.py +0 -0
  198. {modal-1.3.1 → modal-1.3.1.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.3.1
3
+ Version: 1.3.1.dev0
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -20,8 +20,7 @@ Requires-Dist: aiohttp
20
20
  Requires-Dist: cbor2
21
21
  Requires-Dist: certifi
22
22
  Requires-Dist: click~=8.1
23
- Requires-Dist: grpclib<0.4.10,>=0.4.7; python_version < "3.14"
24
- Requires-Dist: grpclib<0.4.10,>=0.4.9; python_version >= "3.14"
23
+ Requires-Dist: grpclib<0.4.10,>=0.4.7
25
24
  Requires-Dist: protobuf!=4.24.0,<7.0,>=3.19
26
25
  Requires-Dist: rich>=12.0.0
27
26
  Requires-Dist: synchronicity~=0.11.1
@@ -41,7 +41,6 @@ try:
41
41
  from .schedule import Cron, Period
42
42
  from .scheduler_placement import SchedulerPlacement
43
43
  from .secret import Secret
44
- from .server import Server
45
44
  from .snapshot import SandboxSnapshot
46
45
  from .volume import Volume
47
46
  except Exception:
@@ -76,7 +75,6 @@ __all__ = [
76
75
  "SandboxSnapshot",
77
76
  "SchedulerPlacement",
78
77
  "Secret",
79
- "Server",
80
78
  "Tunnel",
81
79
  "Volume",
82
80
  "asgi_app",
@@ -35,7 +35,6 @@ def main():
35
35
  ):
36
36
  raise
37
37
 
38
- from rich.markup import escape
39
38
  from rich.panel import Panel
40
39
 
41
40
  title = "Error"
@@ -44,7 +43,7 @@ def main():
44
43
  content = f"{content}\n\nNote: {' '.join(notes)}"
45
44
 
46
45
  console = make_console(stderr=True)
47
- panel = Panel(escape(content), title=title, title_align="left", border_style="red")
46
+ panel = Panel(content, title=title, title_align="left", border_style="red")
48
47
  console.print(panel, highlight=False)
49
48
  sys.exit(1)
50
49
 
@@ -52,15 +52,11 @@ async def _workspace_billing_report(
52
52
  if end is None:
53
53
  end = datetime.now(timezone.utc)
54
54
 
55
- if start.tzinfo is None:
56
- start = start.replace(tzinfo=timezone.utc)
57
- elif start.tzinfo != timezone.utc:
58
- raise InvalidError("Timezone-aware 'start' parameter must be in UTC.")
59
-
60
- if end.tzinfo is None:
61
- end = end.replace(tzinfo=timezone.utc)
62
- elif end.tzinfo != timezone.utc:
63
- raise InvalidError("Timezone-aware 'end' parameter must be in UTC.")
55
+ for dt in (start, end):
56
+ if dt.tzinfo is None:
57
+ dt = dt.replace(tzinfo=timezone.utc)
58
+ elif dt.tzinfo != timezone.utc:
59
+ raise InvalidError("Timezone-aware start/end limits must be in UTC.")
64
60
 
65
61
  request = api_pb2.WorkspaceBillingReportRequest(
66
62
  resolution=resolution,
@@ -5,7 +5,6 @@ import os
5
5
  from modal._runtime.user_code_imports import (
6
6
  Service,
7
7
  import_class_service,
8
- import_server_service,
9
8
  import_single_function_service,
10
9
  )
11
10
 
@@ -301,18 +300,7 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
301
300
  param_kwargs = {}
302
301
 
303
302
  with execution_context._import_context():
304
- if function_def.is_server:
305
- service_base_function_id = container_args.app_layout.function_ids[function_def.function_name]
306
- service_function_hydration_data = [
307
- o for o in container_args.app_layout.objects if o.object_id == service_base_function_id
308
- ][0]
309
- service = import_server_service(
310
- function_def,
311
- service_function_hydration_data,
312
- client,
313
- ser_usr_cls,
314
- )
315
- elif function_def.is_class:
303
+ if function_def.is_class:
316
304
  # this is a bit ugly - match the function and class based on function name to get metadata
317
305
  # This metadata is required in order to hydrate the class in case it's not globally
318
306
  # decorated (or serialized)
@@ -689,7 +689,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
689
689
  nonpreemptible: bool = False,
690
690
  is_builder_function: bool = False,
691
691
  is_auto_snapshot: bool = False,
692
- is_server: bool = False,
693
692
  enable_memory_snapshot: bool = False,
694
693
  block_network: bool = False,
695
694
  restrict_modal_access: bool = False,
@@ -723,6 +722,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
723
722
  f"Function {raw_f} has a schedule, so it needs to support being called with no arguments"
724
723
  )
725
724
  else:
725
+ # must be a "class service function"
726
726
  assert info.user_cls
727
727
  assert not webhook_config
728
728
  assert not schedule
@@ -734,8 +734,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
734
734
  ]
735
735
 
736
736
  retry_policy = _parse_retries(
737
- retries,
738
- f"Function '{info.get_tag()}'" if info.raw_f else f"Class '{info.get_tag()}'",
737
+ retries, f"Function '{info.get_tag()}'" if info.raw_f else f"Class '{info.get_tag()}'"
739
738
  )
740
739
 
741
740
  if retry_policy is not None:
@@ -1055,7 +1054,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1055
1054
  supported_input_formats=supported_input_formats,
1056
1055
  supported_output_formats=supported_output_formats,
1057
1056
  http_config=http_config,
1058
- is_server=is_server,
1059
1057
  )
1060
1058
 
1061
1059
  if isinstance(gpu, list):
@@ -1095,7 +1093,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1095
1093
  supported_input_formats=supported_input_formats,
1096
1094
  supported_output_formats=supported_output_formats,
1097
1095
  http_config=http_config,
1098
- is_server=function_definition.is_server,
1099
1096
  )
1100
1097
 
1101
1098
  ranked_functions = []
@@ -6,7 +6,7 @@ from google.protobuf.message import Message
6
6
  from grpclib import GRPCError, Status
7
7
 
8
8
  from . import exception
9
- from ._traceback import suppress_tb_frame
9
+ from ._traceback import suppress_tb_frames
10
10
  from ._utils.grpc_utils import Retry, _retry_transient_errors
11
11
  from .config import config, logger
12
12
 
@@ -50,7 +50,7 @@ class grpc_error_converter:
50
50
  def __exit__(self, exc_type, exc, traceback) -> Literal[False]:
51
51
  # skip all internal frames from grpclib
52
52
  use_full_traceback = config.get("traceback")
53
- with suppress_tb_frame():
53
+ with suppress_tb_frames(1):
54
54
  if isinstance(exc, GRPCError):
55
55
  modal_exc = _STATUS_TO_EXCEPTION[exc.status](exc.message)
56
56
  modal_exc._grpc_message = exc.message
@@ -115,7 +115,7 @@ class UnaryUnaryWrapper(Generic[RequestType, ResponseType]):
115
115
  timeout: Optional[float] = None,
116
116
  metadata: Optional[list[tuple[str, str]]] = None,
117
117
  ) -> ResponseType:
118
- with suppress_tb_frame():
118
+ with suppress_tb_frames(1):
119
119
  if timeout is not None and retry is not None:
120
120
  raise exception.InvalidError("Retry must be None when timeout is set")
121
121
 
@@ -1,12 +1,9 @@
1
1
  # Copyright Modal Labs 2025
2
- from typing import TYPE_CHECKING, Optional
2
+ from typing import Optional
3
3
 
4
4
  from .client import _Client
5
5
  from .config import config
6
6
 
7
- if TYPE_CHECKING:
8
- from ._utils.async_utils import TaskContext
9
-
10
7
 
11
8
  class LoadContext:
12
9
  """Encapsulates optional metadata values used during object loading.
@@ -18,7 +15,6 @@ class LoadContext:
18
15
  _client: Optional[_Client] = None
19
16
  _environment_name: Optional[str] = None
20
17
  _app_id: Optional[str] = None
21
- _task_context: Optional["TaskContext"] = None
22
18
 
23
19
  def __init__(
24
20
  self,
@@ -26,12 +22,10 @@ class LoadContext:
26
22
  client: Optional[_Client] = None,
27
23
  environment_name: Optional[str] = None,
28
24
  app_id: Optional[str] = None,
29
- task_context: Optional["TaskContext"] = None,
30
25
  ):
31
26
  self._client = client
32
27
  self._environment_name = environment_name
33
28
  self._app_id = app_id
34
- self._task_context = task_context
35
29
 
36
30
  @property
37
31
  def client(self) -> _Client:
@@ -47,11 +41,6 @@ class LoadContext:
47
41
  def app_id(self) -> Optional[str]:
48
42
  return self._app_id
49
43
 
50
- @property
51
- def task_context(self) -> "TaskContext":
52
- assert self._task_context is not None, "LoadContext has no TaskContext"
53
- return self._task_context
54
-
55
44
  @classmethod
56
45
  def empty(cls) -> "LoadContext":
57
46
  """Create an empty LoadContext with all fields set to None.
@@ -70,7 +59,6 @@ class LoadContext:
70
59
  client=self._client if self._client is not None else parent._client,
71
60
  environment_name=self._environment_name if self._environment_name is not None else parent._environment_name,
72
61
  app_id=self._app_id if self._app_id is not None else parent._app_id,
73
- task_context=self._task_context if self._task_context is not None else parent._task_context,
74
62
  ) # TODO (elias): apply_defaults?
75
63
 
76
64
  async def apply_defaults(self) -> "LoadContext":
@@ -83,27 +71,16 @@ class LoadContext:
83
71
  client=self.client if is_valid_client else await _Client.from_env(),
84
72
  environment_name=self._environment_name or config.get("environment") or "",
85
73
  app_id=self._app_id,
86
- task_context=self._task_context,
87
74
  )
88
75
 
89
76
  def reset(self) -> "LoadContext":
90
- """In-place replace all values with None, such that any inferred values/upgrades
91
- will work. This is useful in cases where a load context reference may have leaked
92
- into objects and used/upgraded but you want to make a fresh re-load, e.g. when doing
93
- multiple `app.run()` calls in the same interpreter session.
94
- """
95
77
  self._client = None
96
78
  self._environment_name = None
97
79
  self._app_id = None
98
- self._task_context = None
99
80
  return self
100
81
 
101
82
  async def in_place_upgrade(
102
- self,
103
- client: Optional[_Client] = None,
104
- environment_name: Optional[str] = None,
105
- app_id: Optional[str] = None,
106
- task_context: Optional["TaskContext"] = None,
83
+ self, client: Optional[_Client] = None, environment_name: Optional[str] = None, app_id: Optional[str] = None
107
84
  ) -> "LoadContext":
108
85
  """In-place set values if they aren't already set, or set default values
109
86
 
@@ -126,5 +103,4 @@ class LoadContext:
126
103
  self._client = self._client or client or await _Client.from_env()
127
104
  self._environment_name = self._environment_name or environment_name or config.get("environment") or ""
128
105
  self._app_id = self._app_id or app_id
129
- self._task_context = self._task_context or task_context
130
106
  return self
@@ -9,11 +9,11 @@ from typing import Callable, ClassVar, Optional
9
9
  from google.protobuf.message import Message
10
10
  from typing_extensions import Self
11
11
 
12
- from modal._traceback import suppress_tb_frame
12
+ from modal._traceback import suppress_tb_frames
13
13
 
14
14
  from ._load_context import LoadContext
15
15
  from ._resolver import Resolver
16
- from ._utils.async_utils import TaskContext, aclosing
16
+ from ._utils.async_utils import aclosing
17
17
  from ._utils.deprecation import deprecation_warning
18
18
  from .client import _Client
19
19
  from .config import config, logger
@@ -127,11 +127,9 @@ class _Object:
127
127
  assert isinstance(object_id, str) and self._type_prefix is not None
128
128
  if not object_id.startswith(self._type_prefix):
129
129
  raise ExecutionError(
130
- f"Can not hydrate {type(self)}: "
130
+ f"Can not hydrate {type(self)}:"
131
131
  f" it has type prefix {self._type_prefix}"
132
- f" but the object_id starts with {object_id[:3]}. "
133
- "This usually means the object name was previously used for a different type. "
134
- "Rename the object/app or stop the previous deployment and redeploy."
132
+ f" but the object_id starts with {object_id[:3]}"
135
133
  )
136
134
  self._object_id = object_id
137
135
  self._client = client
@@ -139,7 +137,7 @@ class _Object:
139
137
  self._is_hydrated = True
140
138
 
141
139
  def _hydrate_metadata(self, metadata: Optional[Message]):
142
- # override this if it's a subclass that needs additional data (other than an object_id) for a functioning Handle
140
+ # override this is subclasses that need additional data (other than an object_id) for a functioning Handle
143
141
  pass
144
142
 
145
143
  def _get_metadata(self) -> Optional[Message]:
@@ -316,10 +314,9 @@ class _Object:
316
314
  self._is_hydrated = False # un-hydrate and re-resolve
317
315
  # we don't set an explicit Client here, relying on the default
318
316
  # env client to be applied by LoadContext.apply_default
317
+ root_load_context = LoadContext.empty()
319
318
  resolver = Resolver()
320
- async with TaskContext() as tc:
321
- root_load_context = LoadContext(task_context=tc)
322
- await resolver.load(typing.cast(_Object, self), root_load_context)
319
+ await resolver.load(typing.cast(_Object, self), root_load_context)
323
320
  else:
324
321
  logger.debug(f"reloading non-lazy {self} by replacing client")
325
322
  self._client = client or await _Client.from_env()
@@ -328,13 +325,11 @@ class _Object:
328
325
  elif not self._hydrate_lazily:
329
326
  self._validate_is_hydrated()
330
327
  else:
331
- # Set the client on LoadContext before loading, with a TaskContext for proper
332
- # exception handling when loading shared dependencies
328
+ # Set the client on LoadContext before loading
329
+ root_load_context = LoadContext(client=client)
333
330
  resolver = Resolver()
334
- async with TaskContext() as tc:
335
- root_load_context = LoadContext(client=client, task_context=tc)
336
- with suppress_tb_frame(): # skip this frame by default
337
- await resolver.load(self, root_load_context)
331
+ with suppress_tb_frames(1): # skip this frame by default
332
+ await resolver.load(self, root_load_context)
338
333
  return self
339
334
 
340
335
 
@@ -169,9 +169,9 @@ class OutputManager:
169
169
 
170
170
  @classmethod
171
171
  @contextlib.contextmanager
172
- def enable_output(cls, show_progress: bool = True, show_timestamps: bool = False) -> Generator[None]:
172
+ def enable_output(cls, show_progress: bool = True) -> Generator[None]:
173
173
  if show_progress:
174
- cls._instance = OutputManager(show_timestamps=show_timestamps)
174
+ cls._instance = OutputManager()
175
175
  try:
176
176
  yield
177
177
  finally:
@@ -19,7 +19,7 @@ from ._functions import _Function
19
19
  from ._utils.async_utils import synchronizer
20
20
  from ._utils.deprecation import deprecation_warning
21
21
  from ._utils.function_utils import callable_has_non_self_params
22
- from .config import config
22
+ from .config import config, logger
23
23
  from .exception import InvalidError
24
24
 
25
25
  MAX_MAX_BATCH_SIZE = 1000
@@ -125,6 +125,7 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
125
125
  user_cls: Optional[type] = None # class
126
126
  flags: _PartialFunctionFlags
127
127
  params: _PartialFunctionParams
128
+ registered: bool
128
129
 
129
130
  def __init__(
130
131
  self,
@@ -140,6 +141,7 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
140
141
  self.user_cls = None
141
142
  self.flags = flags
142
143
  self.params = params
144
+ self.registered = False
143
145
  self.validate_flag_composition()
144
146
 
145
147
  def stack(self, flags: _PartialFunctionFlags, params: _PartialFunctionParams) -> typing_extensions.Self:
@@ -154,11 +156,13 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
154
156
  uses_interface_flags = self.flags & _PartialFunctionFlags.interface_flags()
155
157
  uses_lifecycle_flags = self.flags & _PartialFunctionFlags.lifecycle_flags()
156
158
  if uses_interface_flags and uses_lifecycle_flags:
159
+ self.registered = True # Hacky, avoid false-positive warning
157
160
  raise InvalidError("Interface decorators cannot be combined with lifecycle decorators.")
158
161
 
159
162
  has_web_interface = self.flags & _PartialFunctionFlags.WEB_INTERFACE
160
163
  has_callable_interface = self.flags & _PartialFunctionFlags.CALLABLE_INTERFACE
161
164
  if has_web_interface and has_callable_interface:
165
+ self.registered = True # Hacky, avoid false-positive warning
162
166
  raise InvalidError("Callable decorators cannot be combined with web interface decorators.")
163
167
 
164
168
  def validate_obj_compatibility(
@@ -170,17 +174,20 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
170
174
  uses_lifecycle_flags = self.flags & _PartialFunctionFlags.lifecycle_flags()
171
175
  uses_interface_flags = self.flags & _PartialFunctionFlags.interface_flags()
172
176
  if self.user_cls is not None and (uses_lifecycle_flags or uses_interface_flags):
177
+ self.registered = True # Hacky, avoid false-positive warning
173
178
  raise InvalidError(
174
179
  f"Cannot apply `@modal.{decorator_name}` to a class. Hint: consider applying to a method instead."
175
180
  )
176
181
 
177
182
  wrapped_object = self.raw_f or self.user_cls
178
183
  if isinstance(wrapped_object, _Function):
184
+ self.registered = True # Hacky, avoid false-positive warning
179
185
  raise InvalidError(
180
186
  f"Cannot stack `@modal.{decorator_name}` on top of `@app.function`."
181
187
  " Hint: swap the order of the decorators."
182
188
  )
183
189
  elif isinstance(wrapped_object, _Cls):
190
+ self.registered = True # Hacky, avoid false-positive warning
184
191
  raise InvalidError(
185
192
  f"Cannot stack `@modal.{decorator_name}` on top of `@app.cls()`."
186
193
  " Hint: swap the order of the decorators."
@@ -189,12 +196,15 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
189
196
  # Run some assertions about a callable wrappee defined by the specific decorator used
190
197
  if self.raw_f is not None:
191
198
  if not callable(self.raw_f):
199
+ self.registered = True # Hacky, avoid false-positive warning
192
200
  raise InvalidError(f"The object wrapped by `@modal.{decorator_name}` must be callable.")
193
201
 
194
202
  if require_sync and inspect.iscoroutinefunction(self.raw_f):
203
+ self.registered = True # Hacky, avoid false-positive warning
195
204
  raise InvalidError(f"The `@modal.{decorator_name}` decorator can't be applied to an async function.")
196
205
 
197
206
  if require_nullary and callable_has_non_self_params(self.raw_f):
207
+ self.registered = True # Hacky, avoid false-positive warning
198
208
  raise InvalidError(f"Functions decorated by `@modal.{decorator_name}` can't have parameters.")
199
209
 
200
210
  def _get_raw_f(self) -> Callable[P, ReturnType]:
@@ -234,6 +244,17 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
234
244
  # to let it be bound to a variable and become a Function later on
235
245
  return self # type: ignore # this returns a PartialFunction in a special internal case
236
246
 
247
+ def __del__(self):
248
+ if self.registered is False:
249
+ if self.raw_f is not None:
250
+ name, object_type, suggestion = self.raw_f.__name__, "function", "@app.function or @app.cls"
251
+ elif self.user_cls is not None:
252
+ name, object_type, suggestion = self.user_cls.__name__, "class", "@app.cls"
253
+ logger.warning(
254
+ f"The `{name}` {object_type} was never registered with the App."
255
+ f" Did you forget an {suggestion} decorator?"
256
+ )
257
+
237
258
 
238
259
  def _find_partial_methods_for_user_cls(user_cls: type[Any], flags: int) -> dict[str, _PartialFunction]:
239
260
  """Grabs all method on a user class, and returns partials. Includes legacy methods."""
@@ -9,10 +9,11 @@ from collections.abc import Hashable
9
9
  from typing import TYPE_CHECKING, Optional
10
10
 
11
11
  import modal._object
12
- from modal._traceback import suppress_tb_frame
12
+ from modal._traceback import suppress_tb_frames
13
13
  from modal_proto import api_pb2
14
14
 
15
15
  from ._load_context import LoadContext
16
+ from ._utils.async_utils import TaskContext
16
17
 
17
18
  if TYPE_CHECKING:
18
19
  from rich.tree import Tree
@@ -34,7 +35,7 @@ class StatusRow:
34
35
 
35
36
  def warning(self, warning: api_pb2.Warning):
36
37
  if self._step_node is not None:
37
- self._step_node.add(f"⚠️ {warning.message}")
38
+ self._step_node.add(f"[yellow]:warning:[/yellow] {warning.message}")
38
39
 
39
40
  def finish(self, message):
40
41
  if self._step_node is not None and self._spinner is not None:
@@ -119,41 +120,37 @@ class Resolver:
119
120
  if not cached_future:
120
121
  # don't run any awaits within this if-block to prevent race conditions
121
122
  async def loader():
122
- with suppress_tb_frame():
123
- load_context = await obj._load_context_overrides.merged_with(parent_load_context).apply_defaults()
124
-
125
- # Use asyncio.gather here (not TaskContext.gather) - the shared TaskContext
126
- # in load_context handles cancellation at the top level, preventing premature
127
- # cancellation of shared dependencies when sibling tasks fail.
128
- await asyncio.gather(*[self.load(dep, load_context) for dep in obj.deps()])
129
-
130
- # Load the object itself
131
- if not obj._load:
132
- raise Exception(f"Object {obj} has no loader function")
133
-
134
- await obj._load(obj, self, load_context, existing_object_id)
135
-
136
- # Check that the id of functions didn't change
137
- # Persisted refs are ignored because their life cycle is managed independently.
138
- if (
139
- not obj._is_another_app
140
- and existing_object_id is not None
141
- and existing_object_id.startswith("fu-")
142
- and obj.object_id != existing_object_id
143
- ):
144
- raise Exception(
145
- f"Tried creating an object using existing id {existing_object_id} "
146
- f"but it has id {obj.object_id}"
147
- )
148
-
149
- return obj
150
-
151
- # use task_context from load_context to make sure tasks are cleaned up eventually
152
- cached_future = parent_load_context.task_context.create_task(loader())
123
+ load_context = await obj._load_context_overrides.merged_with(parent_load_context).apply_defaults()
124
+
125
+ # TODO(erikbern): do we need existing_object_id for those?
126
+ await TaskContext.gather(*[self.load(dep, load_context) for dep in obj.deps()])
127
+
128
+ # Load the object itself
129
+ if not obj._load:
130
+ raise Exception(f"Object {obj} has no loader function")
131
+
132
+ await obj._load(obj, self, load_context, existing_object_id)
133
+
134
+ # Check that the id of functions didn't change
135
+ # Persisted refs are ignored because their life cycle is managed independently.
136
+ if (
137
+ not obj._is_another_app
138
+ and existing_object_id is not None
139
+ and existing_object_id.startswith("fu-")
140
+ and obj.object_id != existing_object_id
141
+ ):
142
+ raise Exception(
143
+ f"Tried creating an object using existing id {existing_object_id} but it has id {obj.object_id}"
144
+ )
145
+
146
+ return obj
147
+
148
+ cached_future = asyncio.create_task(loader())
153
149
  self._local_uuid_to_future[obj.local_uuid] = cached_future
154
150
  if deduplication_key is not None:
155
151
  self._deduplication_cache[deduplication_key] = cached_future
156
- with suppress_tb_frame():
152
+ with suppress_tb_frames(2):
153
+ # skip current frame + `loader()` closure frame from above
157
154
  return await cached_future
158
155
 
159
156
  def objects(self) -> list["modal._object._Object"]: