modal 0.73.126__tar.gz → 0.73.128__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 (185) hide show
  1. {modal-0.73.126 → modal-0.73.128}/PKG-INFO +1 -1
  2. {modal-0.73.126 → modal-0.73.128}/modal/__init__.py +2 -0
  3. {modal-0.73.126 → modal-0.73.128}/modal/_functions.py +4 -2
  4. {modal-0.73.126 → modal-0.73.128}/modal/_partial_function.py +54 -0
  5. {modal-0.73.126 → modal-0.73.128}/modal/app.py +34 -5
  6. {modal-0.73.126 → modal-0.73.128}/modal/app.pyi +3 -2
  7. {modal-0.73.126 → modal-0.73.128}/modal/client.pyi +2 -2
  8. {modal-0.73.126 → modal-0.73.128}/modal/functions.pyi +2 -1
  9. {modal-0.73.126 → modal-0.73.128}/modal/partial_function.py +2 -0
  10. {modal-0.73.126 → modal-0.73.128}/modal/partial_function.pyi +9 -0
  11. {modal-0.73.126 → modal-0.73.128}/modal/sandbox.py +5 -1
  12. {modal-0.73.126 → modal-0.73.128}/modal.egg-info/PKG-INFO +1 -1
  13. {modal-0.73.126 → modal-0.73.128}/modal_proto/api.proto +2 -2
  14. {modal-0.73.126 → modal-0.73.128}/modal_proto/api_pb2.py +1 -1
  15. {modal-0.73.126 → modal-0.73.128}/modal_version/_version_generated.py +1 -1
  16. {modal-0.73.126 → modal-0.73.128}/LICENSE +0 -0
  17. {modal-0.73.126 → modal-0.73.128}/README.md +0 -0
  18. {modal-0.73.126 → modal-0.73.128}/modal/__main__.py +0 -0
  19. {modal-0.73.126 → modal-0.73.128}/modal/_clustered_functions.py +0 -0
  20. {modal-0.73.126 → modal-0.73.128}/modal/_clustered_functions.pyi +0 -0
  21. {modal-0.73.126 → modal-0.73.128}/modal/_container_entrypoint.py +0 -0
  22. {modal-0.73.126 → modal-0.73.128}/modal/_ipython.py +0 -0
  23. {modal-0.73.126 → modal-0.73.128}/modal/_location.py +0 -0
  24. {modal-0.73.126 → modal-0.73.128}/modal/_object.py +0 -0
  25. {modal-0.73.126 → modal-0.73.128}/modal/_output.py +0 -0
  26. {modal-0.73.126 → modal-0.73.128}/modal/_proxy_tunnel.py +0 -0
  27. {modal-0.73.126 → modal-0.73.128}/modal/_pty.py +0 -0
  28. {modal-0.73.126 → modal-0.73.128}/modal/_resolver.py +0 -0
  29. {modal-0.73.126 → modal-0.73.128}/modal/_resources.py +0 -0
  30. {modal-0.73.126 → modal-0.73.128}/modal/_runtime/__init__.py +0 -0
  31. {modal-0.73.126 → modal-0.73.128}/modal/_runtime/asgi.py +0 -0
  32. {modal-0.73.126 → modal-0.73.128}/modal/_runtime/container_io_manager.py +0 -0
  33. {modal-0.73.126 → modal-0.73.128}/modal/_runtime/container_io_manager.pyi +0 -0
  34. {modal-0.73.126 → modal-0.73.128}/modal/_runtime/execution_context.py +0 -0
  35. {modal-0.73.126 → modal-0.73.128}/modal/_runtime/execution_context.pyi +0 -0
  36. {modal-0.73.126 → modal-0.73.128}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  37. {modal-0.73.126 → modal-0.73.128}/modal/_runtime/telemetry.py +0 -0
  38. {modal-0.73.126 → modal-0.73.128}/modal/_runtime/user_code_imports.py +0 -0
  39. {modal-0.73.126 → modal-0.73.128}/modal/_serialization.py +0 -0
  40. {modal-0.73.126 → modal-0.73.128}/modal/_traceback.py +0 -0
  41. {modal-0.73.126 → modal-0.73.128}/modal/_tunnel.py +0 -0
  42. {modal-0.73.126 → modal-0.73.128}/modal/_tunnel.pyi +0 -0
  43. {modal-0.73.126 → modal-0.73.128}/modal/_utils/__init__.py +0 -0
  44. {modal-0.73.126 → modal-0.73.128}/modal/_utils/app_utils.py +0 -0
  45. {modal-0.73.126 → modal-0.73.128}/modal/_utils/async_utils.py +0 -0
  46. {modal-0.73.126 → modal-0.73.128}/modal/_utils/blob_utils.py +0 -0
  47. {modal-0.73.126 → modal-0.73.128}/modal/_utils/bytes_io_segment_payload.py +0 -0
  48. {modal-0.73.126 → modal-0.73.128}/modal/_utils/deprecation.py +0 -0
  49. {modal-0.73.126 → modal-0.73.128}/modal/_utils/docker_utils.py +0 -0
  50. {modal-0.73.126 → modal-0.73.128}/modal/_utils/function_utils.py +0 -0
  51. {modal-0.73.126 → modal-0.73.128}/modal/_utils/git_utils.py +0 -0
  52. {modal-0.73.126 → modal-0.73.128}/modal/_utils/grpc_testing.py +0 -0
  53. {modal-0.73.126 → modal-0.73.128}/modal/_utils/grpc_utils.py +0 -0
  54. {modal-0.73.126 → modal-0.73.128}/modal/_utils/hash_utils.py +0 -0
  55. {modal-0.73.126 → modal-0.73.128}/modal/_utils/http_utils.py +0 -0
  56. {modal-0.73.126 → modal-0.73.128}/modal/_utils/jwt_utils.py +0 -0
  57. {modal-0.73.126 → modal-0.73.128}/modal/_utils/logger.py +0 -0
  58. {modal-0.73.126 → modal-0.73.128}/modal/_utils/mount_utils.py +0 -0
  59. {modal-0.73.126 → modal-0.73.128}/modal/_utils/name_utils.py +0 -0
  60. {modal-0.73.126 → modal-0.73.128}/modal/_utils/package_utils.py +0 -0
  61. {modal-0.73.126 → modal-0.73.128}/modal/_utils/pattern_utils.py +0 -0
  62. {modal-0.73.126 → modal-0.73.128}/modal/_utils/rand_pb_testing.py +0 -0
  63. {modal-0.73.126 → modal-0.73.128}/modal/_utils/shell_utils.py +0 -0
  64. {modal-0.73.126 → modal-0.73.128}/modal/_vendor/__init__.py +0 -0
  65. {modal-0.73.126 → modal-0.73.128}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  66. {modal-0.73.126 → modal-0.73.128}/modal/_vendor/cloudpickle.py +0 -0
  67. {modal-0.73.126 → modal-0.73.128}/modal/_vendor/tblib.py +0 -0
  68. {modal-0.73.126 → modal-0.73.128}/modal/_watcher.py +0 -0
  69. {modal-0.73.126 → modal-0.73.128}/modal/call_graph.py +0 -0
  70. {modal-0.73.126 → modal-0.73.128}/modal/cli/__init__.py +0 -0
  71. {modal-0.73.126 → modal-0.73.128}/modal/cli/_download.py +0 -0
  72. {modal-0.73.126 → modal-0.73.128}/modal/cli/_traceback.py +0 -0
  73. {modal-0.73.126 → modal-0.73.128}/modal/cli/app.py +0 -0
  74. {modal-0.73.126 → modal-0.73.128}/modal/cli/config.py +0 -0
  75. {modal-0.73.126 → modal-0.73.128}/modal/cli/container.py +0 -0
  76. {modal-0.73.126 → modal-0.73.128}/modal/cli/dict.py +0 -0
  77. {modal-0.73.126 → modal-0.73.128}/modal/cli/entry_point.py +0 -0
  78. {modal-0.73.126 → modal-0.73.128}/modal/cli/environment.py +0 -0
  79. {modal-0.73.126 → modal-0.73.128}/modal/cli/import_refs.py +0 -0
  80. {modal-0.73.126 → modal-0.73.128}/modal/cli/launch.py +0 -0
  81. {modal-0.73.126 → modal-0.73.128}/modal/cli/network_file_system.py +0 -0
  82. {modal-0.73.126 → modal-0.73.128}/modal/cli/profile.py +0 -0
  83. {modal-0.73.126 → modal-0.73.128}/modal/cli/programs/__init__.py +0 -0
  84. {modal-0.73.126 → modal-0.73.128}/modal/cli/programs/run_jupyter.py +0 -0
  85. {modal-0.73.126 → modal-0.73.128}/modal/cli/programs/vscode.py +0 -0
  86. {modal-0.73.126 → modal-0.73.128}/modal/cli/queues.py +0 -0
  87. {modal-0.73.126 → modal-0.73.128}/modal/cli/run.py +0 -0
  88. {modal-0.73.126 → modal-0.73.128}/modal/cli/secret.py +0 -0
  89. {modal-0.73.126 → modal-0.73.128}/modal/cli/token.py +0 -0
  90. {modal-0.73.126 → modal-0.73.128}/modal/cli/utils.py +0 -0
  91. {modal-0.73.126 → modal-0.73.128}/modal/cli/volume.py +0 -0
  92. {modal-0.73.126 → modal-0.73.128}/modal/client.py +0 -0
  93. {modal-0.73.126 → modal-0.73.128}/modal/cloud_bucket_mount.py +0 -0
  94. {modal-0.73.126 → modal-0.73.128}/modal/cloud_bucket_mount.pyi +0 -0
  95. {modal-0.73.126 → modal-0.73.128}/modal/cls.py +0 -0
  96. {modal-0.73.126 → modal-0.73.128}/modal/cls.pyi +0 -0
  97. {modal-0.73.126 → modal-0.73.128}/modal/config.py +0 -0
  98. {modal-0.73.126 → modal-0.73.128}/modal/container_process.py +0 -0
  99. {modal-0.73.126 → modal-0.73.128}/modal/container_process.pyi +0 -0
  100. {modal-0.73.126 → modal-0.73.128}/modal/dict.py +0 -0
  101. {modal-0.73.126 → modal-0.73.128}/modal/dict.pyi +0 -0
  102. {modal-0.73.126 → modal-0.73.128}/modal/environments.py +0 -0
  103. {modal-0.73.126 → modal-0.73.128}/modal/environments.pyi +0 -0
  104. {modal-0.73.126 → modal-0.73.128}/modal/exception.py +0 -0
  105. {modal-0.73.126 → modal-0.73.128}/modal/experimental.py +0 -0
  106. {modal-0.73.126 → modal-0.73.128}/modal/experimental.pyi +0 -0
  107. {modal-0.73.126 → modal-0.73.128}/modal/extensions/__init__.py +0 -0
  108. {modal-0.73.126 → modal-0.73.128}/modal/extensions/ipython.py +0 -0
  109. {modal-0.73.126 → modal-0.73.128}/modal/file_io.py +0 -0
  110. {modal-0.73.126 → modal-0.73.128}/modal/file_io.pyi +0 -0
  111. {modal-0.73.126 → modal-0.73.128}/modal/file_pattern_matcher.py +0 -0
  112. {modal-0.73.126 → modal-0.73.128}/modal/functions.py +0 -0
  113. {modal-0.73.126 → modal-0.73.128}/modal/gpu.py +0 -0
  114. {modal-0.73.126 → modal-0.73.128}/modal/image.py +0 -0
  115. {modal-0.73.126 → modal-0.73.128}/modal/image.pyi +0 -0
  116. {modal-0.73.126 → modal-0.73.128}/modal/io_streams.py +0 -0
  117. {modal-0.73.126 → modal-0.73.128}/modal/io_streams.pyi +0 -0
  118. {modal-0.73.126 → modal-0.73.128}/modal/mount.py +0 -0
  119. {modal-0.73.126 → modal-0.73.128}/modal/mount.pyi +0 -0
  120. {modal-0.73.126 → modal-0.73.128}/modal/network_file_system.py +0 -0
  121. {modal-0.73.126 → modal-0.73.128}/modal/network_file_system.pyi +0 -0
  122. {modal-0.73.126 → modal-0.73.128}/modal/object.py +0 -0
  123. {modal-0.73.126 → modal-0.73.128}/modal/object.pyi +0 -0
  124. {modal-0.73.126 → modal-0.73.128}/modal/output.py +0 -0
  125. {modal-0.73.126 → modal-0.73.128}/modal/parallel_map.py +0 -0
  126. {modal-0.73.126 → modal-0.73.128}/modal/parallel_map.pyi +0 -0
  127. {modal-0.73.126 → modal-0.73.128}/modal/proxy.py +0 -0
  128. {modal-0.73.126 → modal-0.73.128}/modal/proxy.pyi +0 -0
  129. {modal-0.73.126 → modal-0.73.128}/modal/py.typed +0 -0
  130. {modal-0.73.126 → modal-0.73.128}/modal/queue.py +0 -0
  131. {modal-0.73.126 → modal-0.73.128}/modal/queue.pyi +0 -0
  132. {modal-0.73.126 → modal-0.73.128}/modal/requirements/2023.12.312.txt +0 -0
  133. {modal-0.73.126 → modal-0.73.128}/modal/requirements/2023.12.txt +0 -0
  134. {modal-0.73.126 → modal-0.73.128}/modal/requirements/2024.04.txt +0 -0
  135. {modal-0.73.126 → modal-0.73.128}/modal/requirements/2024.10.txt +0 -0
  136. {modal-0.73.126 → modal-0.73.128}/modal/requirements/PREVIEW.txt +0 -0
  137. {modal-0.73.126 → modal-0.73.128}/modal/requirements/README.md +0 -0
  138. {modal-0.73.126 → modal-0.73.128}/modal/requirements/base-images.json +0 -0
  139. {modal-0.73.126 → modal-0.73.128}/modal/retries.py +0 -0
  140. {modal-0.73.126 → modal-0.73.128}/modal/runner.py +0 -0
  141. {modal-0.73.126 → modal-0.73.128}/modal/runner.pyi +0 -0
  142. {modal-0.73.126 → modal-0.73.128}/modal/running_app.py +0 -0
  143. {modal-0.73.126 → modal-0.73.128}/modal/sandbox.pyi +0 -0
  144. {modal-0.73.126 → modal-0.73.128}/modal/schedule.py +0 -0
  145. {modal-0.73.126 → modal-0.73.128}/modal/scheduler_placement.py +0 -0
  146. {modal-0.73.126 → modal-0.73.128}/modal/secret.py +0 -0
  147. {modal-0.73.126 → modal-0.73.128}/modal/secret.pyi +0 -0
  148. {modal-0.73.126 → modal-0.73.128}/modal/serving.py +0 -0
  149. {modal-0.73.126 → modal-0.73.128}/modal/serving.pyi +0 -0
  150. {modal-0.73.126 → modal-0.73.128}/modal/snapshot.py +0 -0
  151. {modal-0.73.126 → modal-0.73.128}/modal/snapshot.pyi +0 -0
  152. {modal-0.73.126 → modal-0.73.128}/modal/stream_type.py +0 -0
  153. {modal-0.73.126 → modal-0.73.128}/modal/token_flow.py +0 -0
  154. {modal-0.73.126 → modal-0.73.128}/modal/token_flow.pyi +0 -0
  155. {modal-0.73.126 → modal-0.73.128}/modal/volume.py +0 -0
  156. {modal-0.73.126 → modal-0.73.128}/modal/volume.pyi +0 -0
  157. {modal-0.73.126 → modal-0.73.128}/modal.egg-info/SOURCES.txt +0 -0
  158. {modal-0.73.126 → modal-0.73.128}/modal.egg-info/dependency_links.txt +0 -0
  159. {modal-0.73.126 → modal-0.73.128}/modal.egg-info/entry_points.txt +0 -0
  160. {modal-0.73.126 → modal-0.73.128}/modal.egg-info/requires.txt +0 -0
  161. {modal-0.73.126 → modal-0.73.128}/modal.egg-info/top_level.txt +0 -0
  162. {modal-0.73.126 → modal-0.73.128}/modal_docs/__init__.py +0 -0
  163. {modal-0.73.126 → modal-0.73.128}/modal_docs/gen_cli_docs.py +0 -0
  164. {modal-0.73.126 → modal-0.73.128}/modal_docs/gen_reference_docs.py +0 -0
  165. {modal-0.73.126 → modal-0.73.128}/modal_docs/mdmd/__init__.py +0 -0
  166. {modal-0.73.126 → modal-0.73.128}/modal_docs/mdmd/mdmd.py +0 -0
  167. {modal-0.73.126 → modal-0.73.128}/modal_docs/mdmd/signatures.py +0 -0
  168. {modal-0.73.126 → modal-0.73.128}/modal_proto/__init__.py +0 -0
  169. {modal-0.73.126 → modal-0.73.128}/modal_proto/api_grpc.py +0 -0
  170. {modal-0.73.126 → modal-0.73.128}/modal_proto/api_pb2.pyi +6 -6
  171. {modal-0.73.126 → modal-0.73.128}/modal_proto/api_pb2_grpc.py +0 -0
  172. {modal-0.73.126 → modal-0.73.128}/modal_proto/api_pb2_grpc.pyi +0 -0
  173. {modal-0.73.126 → modal-0.73.128}/modal_proto/modal_api_grpc.py +0 -0
  174. {modal-0.73.126 → modal-0.73.128}/modal_proto/modal_options_grpc.py +0 -0
  175. {modal-0.73.126 → modal-0.73.128}/modal_proto/options.proto +0 -0
  176. {modal-0.73.126 → modal-0.73.128}/modal_proto/options_grpc.py +0 -0
  177. {modal-0.73.126 → modal-0.73.128}/modal_proto/options_pb2.py +0 -0
  178. {modal-0.73.126 → modal-0.73.128}/modal_proto/options_pb2.pyi +0 -0
  179. {modal-0.73.126 → modal-0.73.128}/modal_proto/options_pb2_grpc.py +0 -0
  180. {modal-0.73.126 → modal-0.73.128}/modal_proto/options_pb2_grpc.pyi +0 -0
  181. {modal-0.73.126 → modal-0.73.128}/modal_proto/py.typed +0 -0
  182. {modal-0.73.126 → modal-0.73.128}/modal_version/__init__.py +0 -0
  183. {modal-0.73.126 → modal-0.73.128}/modal_version/__main__.py +0 -0
  184. {modal-0.73.126 → modal-0.73.128}/pyproject.toml +0 -0
  185. {modal-0.73.126 → modal-0.73.128}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: modal
3
- Version: 0.73.126
3
+ Version: 0.73.128
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -27,6 +27,7 @@ try:
27
27
  asgi_app,
28
28
  batched,
29
29
  build,
30
+ concurrent,
30
31
  enter,
31
32
  exit,
32
33
  fastapi_endpoint,
@@ -82,6 +83,7 @@ __all__ = [
82
83
  "asgi_app",
83
84
  "batched",
84
85
  "build",
86
+ "concurrent",
85
87
  "current_function_call_id",
86
88
  "current_input_id",
87
89
  "enable_output",
@@ -435,7 +435,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
435
435
  max_containers: Optional[int] = None,
436
436
  buffer_containers: Optional[int] = None,
437
437
  scaledown_window: Optional[int] = None,
438
- allow_concurrent_inputs: Optional[int] = None,
438
+ max_concurrent_inputs: Optional[int] = None,
439
+ target_concurrent_inputs: Optional[int] = None,
439
440
  batch_max_size: Optional[int] = None,
440
441
  batch_wait_ms: Optional[int] = None,
441
442
  cloud: Optional[str] = None,
@@ -786,7 +787,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
786
787
  runtime_perf_record=config.get("runtime_perf_record"),
787
788
  app_name=app_name,
788
789
  is_builder_function=is_builder_function,
789
- target_concurrent_inputs=allow_concurrent_inputs or 0,
790
+ max_concurrent_inputs=max_concurrent_inputs or 0,
791
+ target_concurrent_inputs=target_concurrent_inputs or 0,
790
792
  batch_max_size=batch_max_size or 0,
791
793
  batch_linger_ms=batch_wait_ms or 0,
792
794
  worker_id=config.get("worker_id"),
@@ -59,6 +59,8 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
59
59
  force_build: bool
60
60
  cluster_size: Optional[int] # Experimental: Clustered functions
61
61
  build_timeout: Optional[int]
62
+ max_concurrent_inputs: Optional[int]
63
+ target_concurrent_inputs: Optional[int]
62
64
 
63
65
  def __init__(
64
66
  self,
@@ -72,6 +74,8 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
72
74
  cluster_size: Optional[int] = None, # Experimental: Clustered functions
73
75
  force_build: bool = False,
74
76
  build_timeout: Optional[int] = None,
77
+ max_concurrent_inputs: Optional[int] = None,
78
+ target_concurrent_inputs: Optional[int] = None,
75
79
  ):
76
80
  self.raw_f = raw_f
77
81
  self.flags = flags
@@ -89,6 +93,8 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
89
93
  self.cluster_size = cluster_size # Experimental: Clustered functions
90
94
  self.force_build = force_build
91
95
  self.build_timeout = build_timeout
96
+ self.max_concurrent_inputs = max_concurrent_inputs
97
+ self.target_concurrent_inputs = target_concurrent_inputs
92
98
 
93
99
  def _get_raw_f(self) -> Callable[P, ReturnType]:
94
100
  return self.raw_f
@@ -143,6 +149,8 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
143
149
  batch_wait_ms=self.batch_wait_ms,
144
150
  force_build=self.force_build,
145
151
  build_timeout=self.build_timeout,
152
+ max_concurrent_inputs=self.max_concurrent_inputs,
153
+ target_concurrent_inputs=self.target_concurrent_inputs,
146
154
  )
147
155
 
148
156
 
@@ -722,3 +730,49 @@ def _batched(
722
730
  )
723
731
 
724
732
  return wrapper
733
+
734
+
735
+ def _concurrent(
736
+ _warn_parentheses_missing=None,
737
+ *,
738
+ max_inputs: int, # Hard limit on each container's input concurrency
739
+ target_inputs: Optional[int] = None, # Input concurrency that Modal's autoscaler should target
740
+ ) -> Callable[[Union[Callable[..., Any], _PartialFunction]], _PartialFunction]:
741
+ """Decorator that allows individual containers to handle multiple inputs concurrently.
742
+
743
+ The concurrency mechanism depends on whether the function is async or not:
744
+ - Async functions will run inputs on a single thread as asyncio tasks.
745
+ - Synchronous functions will use multi-threading. The code must be thread-safe.
746
+
747
+ Input concurrency will be most useful for workflows that are IO-bound
748
+ (e.g., making network requests) or when running an inference server that supports
749
+ dynamic batching.
750
+
751
+ When `target_inputs` is set, Modal's autoscaler will try to provision resources such
752
+ that each container is running that many inputs concurrently. Containers may burst up to
753
+ up to `max_inputs` if resources are insufficient to remain at the target concurrency.
754
+ """
755
+ if _warn_parentheses_missing is not None:
756
+ raise InvalidError(
757
+ "Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@modal.concurrent()`."
758
+ )
759
+
760
+ if target_inputs and target_inputs > max_inputs:
761
+ raise InvalidError("`target_inputs` parameter cannot be greater than `max_inputs`.")
762
+
763
+ def wrapper(obj: Union[Callable[..., Any], _PartialFunction]) -> _PartialFunction:
764
+ if isinstance(obj, _PartialFunction):
765
+ # Risky that we need to mutate the parameters here; should make this safer
766
+ obj.max_concurrent_inputs = max_inputs
767
+ obj.target_concurrent_inputs = target_inputs
768
+ obj.add_flags(_PartialFunctionFlags.FUNCTION)
769
+ return obj
770
+
771
+ return _PartialFunction(
772
+ obj,
773
+ _PartialFunctionFlags.FUNCTION,
774
+ max_concurrent_inputs=max_inputs,
775
+ target_concurrent_inputs=target_inputs,
776
+ )
777
+
778
+ return wrapper
@@ -678,6 +678,12 @@ class _App:
678
678
  is_generator = f.is_generator
679
679
  batch_max_size = f.batch_max_size
680
680
  batch_wait_ms = f.batch_wait_ms
681
+ if f.max_concurrent_inputs: # Using @modal.concurrent()
682
+ max_concurrent_inputs = f.max_concurrent_inputs
683
+ target_concurrent_inputs = f.target_concurrent_inputs
684
+ else:
685
+ max_concurrent_inputs = allow_concurrent_inputs
686
+ target_concurrent_inputs = None
681
687
  else:
682
688
  if not is_global_object(f.__qualname__) and not serialized:
683
689
  raise InvalidError(
@@ -709,10 +715,12 @@ class _App:
709
715
  )
710
716
 
711
717
  info = FunctionInfo(f, serialized=serialized, name_override=name)
718
+ raw_f = f
712
719
  webhook_config = None
713
720
  batch_max_size = None
714
721
  batch_wait_ms = None
715
- raw_f = f
722
+ max_concurrent_inputs = allow_concurrent_inputs
723
+ target_concurrent_inputs = None
716
724
 
717
725
  cluster_size = None # Experimental: Clustered functions
718
726
  i6pn_enabled = i6pn
@@ -753,7 +761,8 @@ class _App:
753
761
  max_containers=max_containers,
754
762
  buffer_containers=buffer_containers,
755
763
  scaledown_window=scaledown_window,
756
- allow_concurrent_inputs=allow_concurrent_inputs,
764
+ max_concurrent_inputs=max_concurrent_inputs,
765
+ target_concurrent_inputs=target_concurrent_inputs,
757
766
  batch_max_size=batch_max_size,
758
767
  batch_wait_ms=batch_wait_ms,
759
768
  timeout=timeout,
@@ -832,7 +841,7 @@ class _App:
832
841
  concurrency_limit: Optional[int] = None, # Replaced with `max_containers`
833
842
  container_idle_timeout: Optional[int] = None, # Replaced with `scaledown_window`
834
843
  _experimental_buffer_containers: Optional[int] = None, # Now stable API with `buffer_containers`
835
- ) -> Callable[[CLS_T], CLS_T]:
844
+ ) -> Callable[[Union[CLS_T, _PartialFunction]], CLS_T]:
836
845
  """
837
846
  Decorator to register a new Modal [Cls](/docs/reference/modal.Cls) with this App.
838
847
  """
@@ -845,8 +854,21 @@ class _App:
845
854
  raise InvalidError("`region` and `_experimental_scheduler_placement` cannot be used together")
846
855
  scheduler_placement = SchedulerPlacement(region=region)
847
856
 
848
- def wrapper(user_cls: CLS_T) -> CLS_T:
857
+ def wrapper(wrapped_cls: Union[CLS_T, _PartialFunction]) -> CLS_T:
849
858
  # Check if the decorated object is a class
859
+ if isinstance(wrapped_cls, _PartialFunction):
860
+ wrapped_cls.wrapped = True
861
+ user_cls = wrapped_cls.raw_f
862
+ if wrapped_cls.max_concurrent_inputs: # Using @modal.concurrent()
863
+ max_concurrent_inputs = wrapped_cls.max_concurrent_inputs
864
+ target_concurrent_inputs = wrapped_cls.target_concurrent_inputs
865
+ else:
866
+ max_concurrent_inputs = allow_concurrent_inputs
867
+ target_concurrent_inputs = None
868
+ else:
869
+ user_cls = wrapped_cls
870
+ max_concurrent_inputs = allow_concurrent_inputs
871
+ target_concurrent_inputs = None
850
872
  if not inspect.isclass(user_cls):
851
873
  raise TypeError("The @app.cls decorator must be used on a class.")
852
874
 
@@ -871,6 +893,12 @@ class _App:
871
893
  ):
872
894
  raise InvalidError("A class must have `enable_memory_snapshot=True` to use `snap=True` on its methods.")
873
895
 
896
+ for method in _find_partial_methods_for_user_cls(user_cls, _PartialFunctionFlags.FUNCTION).values():
897
+ if method.max_concurrent_inputs:
898
+ raise InvalidError(
899
+ "The `@modal.concurrent` decorator cannot be used on methods; decorate the class instead."
900
+ )
901
+
874
902
  info = FunctionInfo(None, serialized=serialized, user_cls=user_cls)
875
903
 
876
904
  cls_func = _Function.from_local(
@@ -892,7 +920,8 @@ class _App:
892
920
  scaledown_window=scaledown_window,
893
921
  proxy=proxy,
894
922
  retries=retries,
895
- allow_concurrent_inputs=allow_concurrent_inputs,
923
+ max_concurrent_inputs=max_concurrent_inputs,
924
+ target_concurrent_inputs=target_concurrent_inputs,
896
925
  batch_max_size=batch_max_size,
897
926
  batch_wait_ms=batch_wait_ms,
898
927
  timeout=timeout,
@@ -1,6 +1,7 @@
1
1
  import collections.abc
2
2
  import modal._functions
3
3
  import modal._object
4
+ import modal._partial_function
4
5
  import modal._utils.function_utils
5
6
  import modal.client
6
7
  import modal.cloud_bucket_mount
@@ -247,7 +248,7 @@ class _App:
247
248
  concurrency_limit: typing.Optional[int] = None,
248
249
  container_idle_timeout: typing.Optional[int] = None,
249
250
  _experimental_buffer_containers: typing.Optional[int] = None,
250
- ) -> collections.abc.Callable[[CLS_T], CLS_T]: ...
251
+ ) -> collections.abc.Callable[[typing.Union[CLS_T, modal._partial_function._PartialFunction]], CLS_T]: ...
251
252
  async def spawn_sandbox(
252
253
  self,
253
254
  *entrypoint_args: str,
@@ -487,7 +488,7 @@ class App:
487
488
  concurrency_limit: typing.Optional[int] = None,
488
489
  container_idle_timeout: typing.Optional[int] = None,
489
490
  _experimental_buffer_containers: typing.Optional[int] = None,
490
- ) -> collections.abc.Callable[[CLS_T], CLS_T]: ...
491
+ ) -> collections.abc.Callable[[typing.Union[CLS_T, modal.partial_function.PartialFunction]], CLS_T]: ...
491
492
 
492
493
  class __spawn_sandbox_spec(typing_extensions.Protocol[SUPERSELF]):
493
494
  def __call__(
@@ -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 = "0.73.126",
34
+ version: str = "0.73.128",
35
35
  ): ...
36
36
  def is_closed(self) -> bool: ...
37
37
  @property
@@ -93,7 +93,7 @@ class Client:
93
93
  server_url: str,
94
94
  client_type: int,
95
95
  credentials: typing.Optional[tuple[str, str]],
96
- version: str = "0.73.126",
96
+ version: str = "0.73.128",
97
97
  ): ...
98
98
  def is_closed(self) -> bool: ...
99
99
  @property
@@ -82,7 +82,8 @@ class Function(
82
82
  max_containers: typing.Optional[int] = None,
83
83
  buffer_containers: typing.Optional[int] = None,
84
84
  scaledown_window: typing.Optional[int] = None,
85
- allow_concurrent_inputs: typing.Optional[int] = None,
85
+ max_concurrent_inputs: typing.Optional[int] = None,
86
+ target_concurrent_inputs: typing.Optional[int] = None,
86
87
  batch_max_size: typing.Optional[int] = None,
87
88
  batch_wait_ms: typing.Optional[int] = None,
88
89
  cloud: typing.Optional[str] = None,
@@ -5,6 +5,7 @@ from ._partial_function import (
5
5
  _asgi_app,
6
6
  _batched,
7
7
  _build,
8
+ _concurrent,
8
9
  _enter,
9
10
  _exit,
10
11
  _fastapi_endpoint,
@@ -28,3 +29,4 @@ build = synchronize_api(_build, target_module=__name__)
28
29
  enter = synchronize_api(_enter, target_module=__name__)
29
30
  exit = synchronize_api(_exit, target_module=__name__)
30
31
  batched = synchronize_api(_batched, target_module=__name__)
32
+ concurrent = synchronize_api(_concurrent, target_module=__name__)
@@ -18,6 +18,8 @@ class PartialFunction(
18
18
  force_build: bool
19
19
  cluster_size: typing.Optional[int]
20
20
  build_timeout: typing.Optional[int]
21
+ max_concurrent_inputs: typing.Optional[int]
22
+ target_concurrent_inputs: typing.Optional[int]
21
23
 
22
24
  def __init__(
23
25
  self,
@@ -31,6 +33,8 @@ class PartialFunction(
31
33
  cluster_size: typing.Optional[int] = None,
32
34
  force_build: bool = False,
33
35
  build_timeout: typing.Optional[int] = None,
36
+ max_concurrent_inputs: typing.Optional[int] = None,
37
+ target_concurrent_inputs: typing.Optional[int] = None,
34
38
  ): ...
35
39
  def _get_raw_f(self) -> collections.abc.Callable[modal._partial_function.P, modal._partial_function.ReturnType]: ...
36
40
  def _is_web_endpoint(self) -> bool: ...
@@ -118,3 +122,8 @@ def exit(
118
122
  def batched(
119
123
  _warn_parentheses_missing=None, *, max_batch_size: int, wait_ms: int
120
124
  ) -> collections.abc.Callable[[collections.abc.Callable[..., typing.Any]], PartialFunction]: ...
125
+ def concurrent(
126
+ _warn_parentheses_missing=None, *, max_inputs: int, target_inputs: typing.Optional[int] = None
127
+ ) -> collections.abc.Callable[
128
+ [typing.Union[collections.abc.Callable[..., typing.Any], PartialFunction]], PartialFunction
129
+ ]: ...
@@ -44,7 +44,11 @@ _default_image: _Image = _Image.debian_slim()
44
44
  # The maximum number of bytes that can be passed to an exec on Linux.
45
45
  # Though this is technically a 'server side' limit, it is unlikely to change.
46
46
  # getconf ARG_MAX will show this value on a host.
47
- ARG_MAX_BYTES = 2_097_152 # 2MiB
47
+ #
48
+ # By probing in production, the limit is 131072 bytes (2**17).
49
+ # We need some bytes of overhead for the rest of the command line besides the args,
50
+ # e.g. 'runsc exec ...'. So we use 2**16 as the limit.
51
+ ARG_MAX_BYTES = 2**16
48
52
 
49
53
  if TYPE_CHECKING:
50
54
  import modal.app
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: modal
3
- Version: 0.73.126
3
+ Version: 0.73.128
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -1216,7 +1216,7 @@ message Function {
1216
1216
 
1217
1217
  repeated VolumeMount volume_mounts = 33;
1218
1218
 
1219
- uint32 target_concurrent_inputs = 34;
1219
+ uint32 max_concurrent_inputs = 34;
1220
1220
 
1221
1221
  repeated CustomDomainInfo custom_domain_info = 35;
1222
1222
 
@@ -1266,7 +1266,7 @@ message Function {
1266
1266
  uint64 batch_linger_ms = 61; // Miliseconds to block before a response is needed
1267
1267
  bool i6pn_enabled = 62;
1268
1268
  bool _experimental_concurrent_cancellations = 63;
1269
- uint32 max_concurrent_inputs = 64;
1269
+ uint32 target_concurrent_inputs = 64;
1270
1270
 
1271
1271
  // TODO(irfansharif): Remove, once https://github.com/modal-labs/modal/pull/15645 lands.
1272
1272
  bool _experimental_task_templates_enabled = 65; // forces going through the new gpu-fallbacks integration path, even if no fallback options are specified