modal 0.74.5__tar.gz → 0.74.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. {modal-0.74.5 → modal-0.74.7}/PKG-INFO +1 -1
  2. {modal-0.74.5 → modal-0.74.7}/modal/_container_entrypoint.py +12 -2
  3. {modal-0.74.5 → modal-0.74.7}/modal/_resolver.py +12 -0
  4. {modal-0.74.5 → modal-0.74.7}/modal/_runtime/container_io_manager.py +4 -26
  5. {modal-0.74.5 → modal-0.74.7}/modal/_runtime/container_io_manager.pyi +0 -17
  6. {modal-0.74.5 → modal-0.74.7}/modal/_utils/blob_utils.py +2 -0
  7. {modal-0.74.5 → modal-0.74.7}/modal/client.pyi +2 -2
  8. {modal-0.74.5 → modal-0.74.7}/modal/config.py +20 -3
  9. {modal-0.74.5 → modal-0.74.7}/modal/mount.py +12 -0
  10. {modal-0.74.5 → modal-0.74.7}/modal.egg-info/PKG-INFO +1 -1
  11. {modal-0.74.5 → modal-0.74.7}/modal_version/_version_generated.py +1 -1
  12. {modal-0.74.5 → modal-0.74.7}/LICENSE +0 -0
  13. {modal-0.74.5 → modal-0.74.7}/README.md +0 -0
  14. {modal-0.74.5 → modal-0.74.7}/modal/__init__.py +0 -0
  15. {modal-0.74.5 → modal-0.74.7}/modal/__main__.py +0 -0
  16. {modal-0.74.5 → modal-0.74.7}/modal/_clustered_functions.py +0 -0
  17. {modal-0.74.5 → modal-0.74.7}/modal/_clustered_functions.pyi +0 -0
  18. {modal-0.74.5 → modal-0.74.7}/modal/_functions.py +0 -0
  19. {modal-0.74.5 → modal-0.74.7}/modal/_ipython.py +0 -0
  20. {modal-0.74.5 → modal-0.74.7}/modal/_location.py +0 -0
  21. {modal-0.74.5 → modal-0.74.7}/modal/_object.py +0 -0
  22. {modal-0.74.5 → modal-0.74.7}/modal/_output.py +0 -0
  23. {modal-0.74.5 → modal-0.74.7}/modal/_partial_function.py +0 -0
  24. {modal-0.74.5 → modal-0.74.7}/modal/_pty.py +0 -0
  25. {modal-0.74.5 → modal-0.74.7}/modal/_resources.py +0 -0
  26. {modal-0.74.5 → modal-0.74.7}/modal/_runtime/__init__.py +0 -0
  27. {modal-0.74.5 → modal-0.74.7}/modal/_runtime/asgi.py +0 -0
  28. {modal-0.74.5 → modal-0.74.7}/modal/_runtime/execution_context.py +0 -0
  29. {modal-0.74.5 → modal-0.74.7}/modal/_runtime/execution_context.pyi +0 -0
  30. {modal-0.74.5 → modal-0.74.7}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  31. {modal-0.74.5 → modal-0.74.7}/modal/_runtime/telemetry.py +0 -0
  32. {modal-0.74.5 → modal-0.74.7}/modal/_runtime/user_code_imports.py +0 -0
  33. {modal-0.74.5 → modal-0.74.7}/modal/_serialization.py +0 -0
  34. {modal-0.74.5 → modal-0.74.7}/modal/_traceback.py +0 -0
  35. {modal-0.74.5 → modal-0.74.7}/modal/_tunnel.py +0 -0
  36. {modal-0.74.5 → modal-0.74.7}/modal/_tunnel.pyi +0 -0
  37. {modal-0.74.5 → modal-0.74.7}/modal/_type_manager.py +0 -0
  38. {modal-0.74.5 → modal-0.74.7}/modal/_utils/__init__.py +0 -0
  39. {modal-0.74.5 → modal-0.74.7}/modal/_utils/app_utils.py +0 -0
  40. {modal-0.74.5 → modal-0.74.7}/modal/_utils/async_utils.py +0 -0
  41. {modal-0.74.5 → modal-0.74.7}/modal/_utils/bytes_io_segment_payload.py +0 -0
  42. {modal-0.74.5 → modal-0.74.7}/modal/_utils/deprecation.py +0 -0
  43. {modal-0.74.5 → modal-0.74.7}/modal/_utils/docker_utils.py +0 -0
  44. {modal-0.74.5 → modal-0.74.7}/modal/_utils/function_utils.py +0 -0
  45. {modal-0.74.5 → modal-0.74.7}/modal/_utils/git_utils.py +0 -0
  46. {modal-0.74.5 → modal-0.74.7}/modal/_utils/grpc_testing.py +0 -0
  47. {modal-0.74.5 → modal-0.74.7}/modal/_utils/grpc_utils.py +0 -0
  48. {modal-0.74.5 → modal-0.74.7}/modal/_utils/hash_utils.py +0 -0
  49. {modal-0.74.5 → modal-0.74.7}/modal/_utils/http_utils.py +0 -0
  50. {modal-0.74.5 → modal-0.74.7}/modal/_utils/jwt_utils.py +0 -0
  51. {modal-0.74.5 → modal-0.74.7}/modal/_utils/logger.py +0 -0
  52. {modal-0.74.5 → modal-0.74.7}/modal/_utils/mount_utils.py +0 -0
  53. {modal-0.74.5 → modal-0.74.7}/modal/_utils/name_utils.py +0 -0
  54. {modal-0.74.5 → modal-0.74.7}/modal/_utils/package_utils.py +0 -0
  55. {modal-0.74.5 → modal-0.74.7}/modal/_utils/pattern_utils.py +0 -0
  56. {modal-0.74.5 → modal-0.74.7}/modal/_utils/rand_pb_testing.py +0 -0
  57. {modal-0.74.5 → modal-0.74.7}/modal/_utils/shell_utils.py +0 -0
  58. {modal-0.74.5 → modal-0.74.7}/modal/_vendor/__init__.py +0 -0
  59. {modal-0.74.5 → modal-0.74.7}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  60. {modal-0.74.5 → modal-0.74.7}/modal/_vendor/cloudpickle.py +0 -0
  61. {modal-0.74.5 → modal-0.74.7}/modal/_vendor/tblib.py +0 -0
  62. {modal-0.74.5 → modal-0.74.7}/modal/_watcher.py +0 -0
  63. {modal-0.74.5 → modal-0.74.7}/modal/app.py +0 -0
  64. {modal-0.74.5 → modal-0.74.7}/modal/app.pyi +0 -0
  65. {modal-0.74.5 → modal-0.74.7}/modal/call_graph.py +0 -0
  66. {modal-0.74.5 → modal-0.74.7}/modal/cli/__init__.py +0 -0
  67. {modal-0.74.5 → modal-0.74.7}/modal/cli/_download.py +0 -0
  68. {modal-0.74.5 → modal-0.74.7}/modal/cli/_traceback.py +0 -0
  69. {modal-0.74.5 → modal-0.74.7}/modal/cli/app.py +0 -0
  70. {modal-0.74.5 → modal-0.74.7}/modal/cli/config.py +0 -0
  71. {modal-0.74.5 → modal-0.74.7}/modal/cli/container.py +0 -0
  72. {modal-0.74.5 → modal-0.74.7}/modal/cli/dict.py +0 -0
  73. {modal-0.74.5 → modal-0.74.7}/modal/cli/entry_point.py +0 -0
  74. {modal-0.74.5 → modal-0.74.7}/modal/cli/environment.py +0 -0
  75. {modal-0.74.5 → modal-0.74.7}/modal/cli/import_refs.py +0 -0
  76. {modal-0.74.5 → modal-0.74.7}/modal/cli/launch.py +0 -0
  77. {modal-0.74.5 → modal-0.74.7}/modal/cli/network_file_system.py +0 -0
  78. {modal-0.74.5 → modal-0.74.7}/modal/cli/profile.py +0 -0
  79. {modal-0.74.5 → modal-0.74.7}/modal/cli/programs/__init__.py +0 -0
  80. {modal-0.74.5 → modal-0.74.7}/modal/cli/programs/run_jupyter.py +0 -0
  81. {modal-0.74.5 → modal-0.74.7}/modal/cli/programs/vscode.py +0 -0
  82. {modal-0.74.5 → modal-0.74.7}/modal/cli/queues.py +0 -0
  83. {modal-0.74.5 → modal-0.74.7}/modal/cli/run.py +0 -0
  84. {modal-0.74.5 → modal-0.74.7}/modal/cli/secret.py +0 -0
  85. {modal-0.74.5 → modal-0.74.7}/modal/cli/token.py +0 -0
  86. {modal-0.74.5 → modal-0.74.7}/modal/cli/utils.py +0 -0
  87. {modal-0.74.5 → modal-0.74.7}/modal/cli/volume.py +0 -0
  88. {modal-0.74.5 → modal-0.74.7}/modal/client.py +0 -0
  89. {modal-0.74.5 → modal-0.74.7}/modal/cloud_bucket_mount.py +0 -0
  90. {modal-0.74.5 → modal-0.74.7}/modal/cloud_bucket_mount.pyi +0 -0
  91. {modal-0.74.5 → modal-0.74.7}/modal/cls.py +0 -0
  92. {modal-0.74.5 → modal-0.74.7}/modal/cls.pyi +0 -0
  93. {modal-0.74.5 → modal-0.74.7}/modal/container_process.py +0 -0
  94. {modal-0.74.5 → modal-0.74.7}/modal/container_process.pyi +0 -0
  95. {modal-0.74.5 → modal-0.74.7}/modal/dict.py +0 -0
  96. {modal-0.74.5 → modal-0.74.7}/modal/dict.pyi +0 -0
  97. {modal-0.74.5 → modal-0.74.7}/modal/environments.py +0 -0
  98. {modal-0.74.5 → modal-0.74.7}/modal/environments.pyi +0 -0
  99. {modal-0.74.5 → modal-0.74.7}/modal/exception.py +0 -0
  100. {modal-0.74.5 → modal-0.74.7}/modal/experimental/__init__.py +0 -0
  101. {modal-0.74.5 → modal-0.74.7}/modal/experimental/ipython.py +0 -0
  102. {modal-0.74.5 → modal-0.74.7}/modal/file_io.py +0 -0
  103. {modal-0.74.5 → modal-0.74.7}/modal/file_io.pyi +0 -0
  104. {modal-0.74.5 → modal-0.74.7}/modal/file_pattern_matcher.py +0 -0
  105. {modal-0.74.5 → modal-0.74.7}/modal/functions.py +0 -0
  106. {modal-0.74.5 → modal-0.74.7}/modal/functions.pyi +0 -0
  107. {modal-0.74.5 → modal-0.74.7}/modal/gpu.py +0 -0
  108. {modal-0.74.5 → modal-0.74.7}/modal/image.py +0 -0
  109. {modal-0.74.5 → modal-0.74.7}/modal/image.pyi +0 -0
  110. {modal-0.74.5 → modal-0.74.7}/modal/io_streams.py +0 -0
  111. {modal-0.74.5 → modal-0.74.7}/modal/io_streams.pyi +0 -0
  112. {modal-0.74.5 → modal-0.74.7}/modal/mount.pyi +0 -0
  113. {modal-0.74.5 → modal-0.74.7}/modal/network_file_system.py +0 -0
  114. {modal-0.74.5 → modal-0.74.7}/modal/network_file_system.pyi +0 -0
  115. {modal-0.74.5 → modal-0.74.7}/modal/object.py +0 -0
  116. {modal-0.74.5 → modal-0.74.7}/modal/object.pyi +0 -0
  117. {modal-0.74.5 → modal-0.74.7}/modal/output.py +0 -0
  118. {modal-0.74.5 → modal-0.74.7}/modal/parallel_map.py +0 -0
  119. {modal-0.74.5 → modal-0.74.7}/modal/parallel_map.pyi +0 -0
  120. {modal-0.74.5 → modal-0.74.7}/modal/partial_function.py +0 -0
  121. {modal-0.74.5 → modal-0.74.7}/modal/partial_function.pyi +0 -0
  122. {modal-0.74.5 → modal-0.74.7}/modal/proxy.py +0 -0
  123. {modal-0.74.5 → modal-0.74.7}/modal/proxy.pyi +0 -0
  124. {modal-0.74.5 → modal-0.74.7}/modal/py.typed +0 -0
  125. {modal-0.74.5 → modal-0.74.7}/modal/queue.py +0 -0
  126. {modal-0.74.5 → modal-0.74.7}/modal/queue.pyi +0 -0
  127. {modal-0.74.5 → modal-0.74.7}/modal/requirements/2023.12.312.txt +0 -0
  128. {modal-0.74.5 → modal-0.74.7}/modal/requirements/2023.12.txt +0 -0
  129. {modal-0.74.5 → modal-0.74.7}/modal/requirements/2024.04.txt +0 -0
  130. {modal-0.74.5 → modal-0.74.7}/modal/requirements/2024.10.txt +0 -0
  131. {modal-0.74.5 → modal-0.74.7}/modal/requirements/PREVIEW.txt +0 -0
  132. {modal-0.74.5 → modal-0.74.7}/modal/requirements/README.md +0 -0
  133. {modal-0.74.5 → modal-0.74.7}/modal/requirements/base-images.json +0 -0
  134. {modal-0.74.5 → modal-0.74.7}/modal/retries.py +0 -0
  135. {modal-0.74.5 → modal-0.74.7}/modal/runner.py +0 -0
  136. {modal-0.74.5 → modal-0.74.7}/modal/runner.pyi +0 -0
  137. {modal-0.74.5 → modal-0.74.7}/modal/running_app.py +0 -0
  138. {modal-0.74.5 → modal-0.74.7}/modal/sandbox.py +0 -0
  139. {modal-0.74.5 → modal-0.74.7}/modal/sandbox.pyi +0 -0
  140. {modal-0.74.5 → modal-0.74.7}/modal/schedule.py +0 -0
  141. {modal-0.74.5 → modal-0.74.7}/modal/scheduler_placement.py +0 -0
  142. {modal-0.74.5 → modal-0.74.7}/modal/secret.py +0 -0
  143. {modal-0.74.5 → modal-0.74.7}/modal/secret.pyi +0 -0
  144. {modal-0.74.5 → modal-0.74.7}/modal/serving.py +0 -0
  145. {modal-0.74.5 → modal-0.74.7}/modal/serving.pyi +0 -0
  146. {modal-0.74.5 → modal-0.74.7}/modal/snapshot.py +0 -0
  147. {modal-0.74.5 → modal-0.74.7}/modal/snapshot.pyi +0 -0
  148. {modal-0.74.5 → modal-0.74.7}/modal/stream_type.py +0 -0
  149. {modal-0.74.5 → modal-0.74.7}/modal/token_flow.py +0 -0
  150. {modal-0.74.5 → modal-0.74.7}/modal/token_flow.pyi +0 -0
  151. {modal-0.74.5 → modal-0.74.7}/modal/volume.py +0 -0
  152. {modal-0.74.5 → modal-0.74.7}/modal/volume.pyi +0 -0
  153. {modal-0.74.5 → modal-0.74.7}/modal.egg-info/SOURCES.txt +0 -0
  154. {modal-0.74.5 → modal-0.74.7}/modal.egg-info/dependency_links.txt +0 -0
  155. {modal-0.74.5 → modal-0.74.7}/modal.egg-info/entry_points.txt +0 -0
  156. {modal-0.74.5 → modal-0.74.7}/modal.egg-info/requires.txt +0 -0
  157. {modal-0.74.5 → modal-0.74.7}/modal.egg-info/top_level.txt +0 -0
  158. {modal-0.74.5 → modal-0.74.7}/modal_docs/__init__.py +0 -0
  159. {modal-0.74.5 → modal-0.74.7}/modal_docs/gen_cli_docs.py +0 -0
  160. {modal-0.74.5 → modal-0.74.7}/modal_docs/gen_reference_docs.py +0 -0
  161. {modal-0.74.5 → modal-0.74.7}/modal_docs/mdmd/__init__.py +0 -0
  162. {modal-0.74.5 → modal-0.74.7}/modal_docs/mdmd/mdmd.py +0 -0
  163. {modal-0.74.5 → modal-0.74.7}/modal_docs/mdmd/signatures.py +0 -0
  164. {modal-0.74.5 → modal-0.74.7}/modal_proto/__init__.py +0 -0
  165. {modal-0.74.5 → modal-0.74.7}/modal_proto/api.proto +0 -0
  166. {modal-0.74.5 → modal-0.74.7}/modal_proto/api_grpc.py +0 -0
  167. {modal-0.74.5 → modal-0.74.7}/modal_proto/api_pb2.py +0 -0
  168. {modal-0.74.5 → modal-0.74.7}/modal_proto/api_pb2.pyi +0 -0
  169. {modal-0.74.5 → modal-0.74.7}/modal_proto/api_pb2_grpc.py +0 -0
  170. {modal-0.74.5 → modal-0.74.7}/modal_proto/api_pb2_grpc.pyi +0 -0
  171. {modal-0.74.5 → modal-0.74.7}/modal_proto/modal_api_grpc.py +0 -0
  172. {modal-0.74.5 → modal-0.74.7}/modal_proto/modal_options_grpc.py +0 -0
  173. {modal-0.74.5 → modal-0.74.7}/modal_proto/options.proto +0 -0
  174. {modal-0.74.5 → modal-0.74.7}/modal_proto/options_grpc.py +0 -0
  175. {modal-0.74.5 → modal-0.74.7}/modal_proto/options_pb2.py +0 -0
  176. {modal-0.74.5 → modal-0.74.7}/modal_proto/options_pb2.pyi +0 -0
  177. {modal-0.74.5 → modal-0.74.7}/modal_proto/options_pb2_grpc.py +0 -0
  178. {modal-0.74.5 → modal-0.74.7}/modal_proto/options_pb2_grpc.pyi +0 -0
  179. {modal-0.74.5 → modal-0.74.7}/modal_proto/py.typed +0 -0
  180. {modal-0.74.5 → modal-0.74.7}/modal_version/__init__.py +0 -0
  181. {modal-0.74.5 → modal-0.74.7}/modal_version/__main__.py +0 -0
  182. {modal-0.74.5 → modal-0.74.7}/pyproject.toml +0 -0
  183. {modal-0.74.5 → modal-0.74.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 0.74.5
3
+ Version: 0.74.7
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -33,7 +33,7 @@ from modal._partial_function import (
33
33
  _find_callables_for_obj,
34
34
  _PartialFunctionFlags,
35
35
  )
36
- from modal._serialization import deserialize_params
36
+ from modal._serialization import deserialize, deserialize_params
37
37
  from modal._utils.async_utils import TaskContext, synchronizer
38
38
  from modal._utils.function_utils import (
39
39
  callable_has_non_self_params,
@@ -394,7 +394,17 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
394
394
  with container_io_manager.heartbeats(is_snapshotting_function), UserCodeEventLoop() as event_loop:
395
395
  # If this is a serialized function, fetch the definition from the server
396
396
  if function_def.definition_type == api_pb2.Function.DEFINITION_TYPE_SERIALIZED:
397
- ser_usr_cls, ser_fun = container_io_manager.get_serialized_function()
397
+ assert function_def.function_serialized or function_def.class_serialized
398
+
399
+ if function_def.function_serialized:
400
+ ser_fun = deserialize(function_def.function_serialized, _client)
401
+ else:
402
+ ser_fun = None
403
+
404
+ if function_def.class_serialized:
405
+ ser_usr_cls = deserialize(function_def.class_serialized, _client)
406
+ else:
407
+ ser_usr_cls = None
398
408
  else:
399
409
  ser_usr_cls, ser_fun = None, None
400
410
 
@@ -1,6 +1,8 @@
1
1
  # Copyright Modal Labs 2023
2
2
  import asyncio
3
3
  import contextlib
4
+ import os
5
+ import tempfile
4
6
  import typing
5
7
  from asyncio import Future
6
8
  from collections.abc import Hashable
@@ -46,6 +48,7 @@ class Resolver:
46
48
  _app_id: Optional[str]
47
49
  _deduplication_cache: dict[Hashable, Future]
48
50
  _client: _Client
51
+ _build_start: float
49
52
 
50
53
  def __init__(
51
54
  self,
@@ -73,6 +76,11 @@ class Resolver:
73
76
  self._environment_name = environment_name
74
77
  self._deduplication_cache = {}
75
78
 
79
+ with tempfile.TemporaryFile() as temp_file:
80
+ # Use file mtime to track build start time because we will later compare this baseline
81
+ # to the mtime on mounted files, and want those measurements to have the same resolution.
82
+ self._build_start = os.fstat(temp_file.fileno()).st_mtime
83
+
76
84
  @property
77
85
  def app_id(self) -> Optional[str]:
78
86
  return self._app_id
@@ -85,6 +93,10 @@ class Resolver:
85
93
  def environment_name(self):
86
94
  return self._environment_name
87
95
 
96
+ @property
97
+ def build_start(self) -> float:
98
+ return self._build_start
99
+
88
100
  async def preload(self, obj, existing_object_id: Optional[str]):
89
101
  if obj._preload is not None:
90
102
  await obj._preload(obj, self, existing_object_id)
@@ -450,28 +450,6 @@ class _ContainerIOManager:
450
450
 
451
451
  await asyncio.sleep(DYNAMIC_CONCURRENCY_INTERVAL_SECS)
452
452
 
453
- async def get_serialized_function(self) -> tuple[Optional[Any], Optional[Callable[..., Any]]]:
454
- # Fetch the serialized function definition
455
- request = api_pb2.FunctionGetSerializedRequest(function_id=self.function_id)
456
- response = await self._client.stub.FunctionGetSerialized(request)
457
- if response.function_serialized:
458
- fun = self.deserialize(response.function_serialized)
459
- else:
460
- fun = None
461
-
462
- if response.class_serialized:
463
- cls = self.deserialize(response.class_serialized)
464
- else:
465
- cls = None
466
-
467
- return cls, fun
468
-
469
- def serialize(self, obj: Any) -> bytes:
470
- return serialize(obj)
471
-
472
- def deserialize(self, data: bytes) -> Any:
473
- return deserialize(data, self._client)
474
-
475
453
  @synchronizer.no_io_translation
476
454
  def serialize_data_format(self, obj: Any, data_format: int) -> bytes:
477
455
  return serialize_data_format(obj, data_format)
@@ -680,20 +658,20 @@ class _ContainerIOManager:
680
658
 
681
659
  def serialize_exception(self, exc: BaseException) -> bytes:
682
660
  try:
683
- return self.serialize(exc)
661
+ return serialize(exc)
684
662
  except Exception as serialization_exc:
685
663
  # We can't always serialize exceptions.
686
664
  err = f"Failed to serialize exception {exc} of type {type(exc)}: {serialization_exc}"
687
665
  logger.info(err)
688
- return self.serialize(SerializationError(err))
666
+ return serialize(SerializationError(err))
689
667
 
690
668
  def serialize_traceback(self, exc: BaseException) -> tuple[Optional[bytes], Optional[bytes]]:
691
669
  serialized_tb, tb_line_cache = None, None
692
670
 
693
671
  try:
694
672
  tb_dict, line_cache = extract_traceback(exc, self.task_id)
695
- serialized_tb = self.serialize(tb_dict)
696
- tb_line_cache = self.serialize(line_cache)
673
+ serialized_tb = serialize(tb_dict)
674
+ tb_line_cache = serialize(line_cache)
697
675
  except Exception:
698
676
  logger.info("Failed to serialize exception traceback.")
699
677
 
@@ -100,11 +100,6 @@ class _ContainerIOManager:
100
100
  def stop_heartbeat(self): ...
101
101
  def dynamic_concurrency_manager(self) -> typing.AsyncContextManager[None]: ...
102
102
  async def _dynamic_concurrency_loop(self): ...
103
- async def get_serialized_function(
104
- self,
105
- ) -> tuple[typing.Optional[typing.Any], typing.Optional[collections.abc.Callable[..., typing.Any]]]: ...
106
- def serialize(self, obj: typing.Any) -> bytes: ...
107
- def deserialize(self, data: bytes) -> typing.Any: ...
108
103
  def serialize_data_format(self, obj: typing.Any, data_format: int) -> bytes: ...
109
104
  async def format_blob_data(self, data: bytes) -> dict[str, typing.Any]: ...
110
105
  def get_data_in(self, function_call_id: str) -> collections.abc.AsyncIterator[typing.Any]: ...
@@ -234,18 +229,6 @@ class ContainerIOManager:
234
229
 
235
230
  _dynamic_concurrency_loop: ___dynamic_concurrency_loop_spec[typing_extensions.Self]
236
231
 
237
- class __get_serialized_function_spec(typing_extensions.Protocol[SUPERSELF]):
238
- def __call__(
239
- self,
240
- ) -> tuple[typing.Optional[typing.Any], typing.Optional[collections.abc.Callable[..., typing.Any]]]: ...
241
- async def aio(
242
- self,
243
- ) -> tuple[typing.Optional[typing.Any], typing.Optional[collections.abc.Callable[..., typing.Any]]]: ...
244
-
245
- get_serialized_function: __get_serialized_function_spec[typing_extensions.Self]
246
-
247
- def serialize(self, obj: typing.Any) -> bytes: ...
248
- def deserialize(self, data: bytes) -> typing.Any: ...
249
232
  def serialize_data_format(self, obj: typing.Any, data_format: int) -> bytes: ...
250
233
 
251
234
  class __format_blob_data_spec(typing_extensions.Protocol[SUPERSELF]):
@@ -292,6 +292,7 @@ async def blob_iter(blob_id: str, stub: ModalClientModal) -> AsyncIterator[bytes
292
292
  class FileUploadSpec:
293
293
  source: Callable[[], Union[AbstractContextManager, BinaryIO]]
294
294
  source_description: Any
295
+ source_is_path: bool
295
296
  mount_filename: str
296
297
 
297
298
  use_blob: bool
@@ -328,6 +329,7 @@ def _get_file_upload_spec(
328
329
  return FileUploadSpec(
329
330
  source=source,
330
331
  source_description=source_description,
332
+ source_is_path=isinstance(source_description, Path),
331
333
  mount_filename=mount_filename.as_posix(),
332
334
  use_blob=use_blob,
333
335
  content=content,
@@ -27,7 +27,7 @@ class _Client:
27
27
  _snapshotted: bool
28
28
 
29
29
  def __init__(
30
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.5"
30
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.7"
31
31
  ): ...
32
32
  def is_closed(self) -> bool: ...
33
33
  @property
@@ -85,7 +85,7 @@ class Client:
85
85
  _snapshotted: bool
86
86
 
87
87
  def __init__(
88
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.5"
88
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.7"
89
89
  ): ...
90
90
  def is_closed(self) -> bool: ...
91
91
  @property
@@ -87,7 +87,7 @@ import os
87
87
  import typing
88
88
  import warnings
89
89
  from textwrap import dedent
90
- from typing import Any, Optional
90
+ from typing import Any, Callable, Optional
91
91
 
92
92
  from google.protobuf.empty_pb2 import Empty
93
93
 
@@ -199,6 +199,15 @@ def _to_boolean(x: object) -> bool:
199
199
  return str(x).lower() not in {"", "0", "false"}
200
200
 
201
201
 
202
+ def _check_value(options: list[str]) -> Callable[[str], str]:
203
+ def checker(x: str) -> str:
204
+ if x not in options:
205
+ raise ValueError(f"Must be one of {options}.")
206
+ return x
207
+
208
+ return checker
209
+
210
+
202
211
  class _Setting(typing.NamedTuple):
203
212
  default: typing.Any = None
204
213
  transform: typing.Callable[[str], typing.Any] = lambda x: x # noqa: E731
@@ -232,6 +241,7 @@ _SETTINGS = {
232
241
  "snapshot_debug": _Setting(False, transform=_to_boolean),
233
242
  "cuda_checkpoint_path": _Setting("/__modal/.bin/cuda-checkpoint"), # Used for snapshotting GPU memory.
234
243
  "function_schemas": _Setting(False, transform=_to_boolean),
244
+ "build_validation": _Setting("error", transform=_check_value(["error", "warn", "ignore"])),
235
245
  }
236
246
 
237
247
 
@@ -253,10 +263,17 @@ class Config:
253
263
  profile = _profile
254
264
  s = _SETTINGS[key]
255
265
  env_var_key = "MODAL_" + key.upper()
266
+
267
+ def transform(val: str) -> Any:
268
+ try:
269
+ return s.transform(val)
270
+ except Exception as e:
271
+ raise InvalidError(f"Invalid value for {key} config ({val!r}): {e}")
272
+
256
273
  if use_env and env_var_key in os.environ:
257
- return s.transform(os.environ[env_var_key])
274
+ return transform(os.environ[env_var_key])
258
275
  elif profile in _user_config and key in _user_config[profile]:
259
- return s.transform(_user_config[profile][key])
276
+ return transform(_user_config[profile][key])
260
277
  else:
261
278
  return s.default
262
279
 
@@ -9,6 +9,7 @@ import sys
9
9
  import sysconfig
10
10
  import time
11
11
  import typing
12
+ import warnings
12
13
  from collections.abc import AsyncGenerator
13
14
  from pathlib import Path, PurePosixPath
14
15
  from typing import Callable, Optional, Sequence, Union
@@ -532,6 +533,17 @@ class _Mount(_Object, type_prefix="mo"):
532
533
  n_finished += 1
533
534
  return mount_file
534
535
 
536
+ # Try to catch cases where user modified their local files (e.g. changed git branches)
537
+ # between triggering a build and Modal actually uploading the file
538
+ if config.get("build_validation") != "ignore" and file_spec.source_is_path:
539
+ mtime = os.stat(file_spec.source_description).st_mtime
540
+ if mtime > resolver.build_start:
541
+ msg = f"{file_spec.source_description} was modified during build process."
542
+ if config.get("build_validation") == "error":
543
+ raise modal.exception.ExecutionError(msg)
544
+ elif config.get("build_validation") == "warn":
545
+ warnings.warn(msg)
546
+
535
547
  request = api_pb2.MountPutFileRequest(sha256_hex=file_spec.sha256_hex)
536
548
  accounted_hashes.add(file_spec.sha256_hex)
537
549
  response = await retry_transient_errors(resolver.client.stub.MountPutFile, request, base_delay=1)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 0.74.5
3
+ Version: 0.74.7
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 5 # git: 2e65784
4
+ build_number = 7 # git: 52311ba
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
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
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