modAL 1.3.6.dev13__py3-none-any.whl → 1.3.6.dev14__py3-none-any.whl

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 (49) hide show
  1. modal/_functions.py +7 -87
  2. modal/_partial_function.py +7 -63
  3. modal/_resources.py +3 -4
  4. modal/_utils/deprecation.py +23 -44
  5. modal/_utils/function_utils.py +17 -0
  6. modal/app.py +11 -85
  7. modal/app.pyi +6 -36
  8. modal/cli/entry_point.py +1 -1
  9. modal/cli/import_refs.py +2 -5
  10. modal/client.pyi +2 -2
  11. modal/cls.py +3 -48
  12. modal/cls.pyi +2 -71
  13. modal/dict.py +3 -21
  14. modal/dict.pyi +5 -9
  15. modal/experimental/__init__.py +9 -87
  16. modal/functions.py +1 -2
  17. modal/functions.pyi +1 -66
  18. modal/image.py +37 -38
  19. modal/image.pyi +26 -27
  20. modal/mount.py +1 -0
  21. modal/network_file_system.py +1 -5
  22. modal/network_file_system.pyi +0 -5
  23. modal/partial_function.pyi +2 -33
  24. modal/queue.py +1 -3
  25. modal/queue.pyi +0 -2
  26. modal/runner.py +7 -9
  27. modal/runner.pyi +8 -8
  28. modal/sandbox.py +3 -23
  29. modal/sandbox.pyi +8 -47
  30. modal/secret.py +3 -12
  31. modal/secret.pyi +0 -13
  32. modal/serving.py +1 -1
  33. modal/volume.py +2 -5
  34. modal/volume.pyi +0 -5
  35. {modal-1.3.6.dev13.dist-info → modal-1.3.6.dev14.dist-info}/METADATA +1 -1
  36. {modal-1.3.6.dev13.dist-info → modal-1.3.6.dev14.dist-info}/RECORD +48 -49
  37. modal_docs/gen_reference_docs.py +0 -1
  38. modal_proto/api_grpc.py +16 -0
  39. modal_proto/api_pb2.py +494 -453
  40. modal_proto/api_pb2.pyi +101 -12
  41. modal_proto/api_pb2_grpc.py +33 -0
  42. modal_proto/api_pb2_grpc.pyi +10 -0
  43. modal_proto/modal_api_grpc.py +1 -0
  44. modal_version/__init__.py +1 -1
  45. modal/gpu.py +0 -225
  46. {modal-1.3.6.dev13.dist-info → modal-1.3.6.dev14.dist-info}/WHEEL +0 -0
  47. {modal-1.3.6.dev13.dist-info → modal-1.3.6.dev14.dist-info}/entry_points.txt +0 -0
  48. {modal-1.3.6.dev13.dist-info → modal-1.3.6.dev14.dist-info}/licenses/LICENSE +0 -0
  49. {modal-1.3.6.dev13.dist-info → modal-1.3.6.dev14.dist-info}/top_level.txt +0 -0
modal/_functions.py CHANGED
@@ -2,7 +2,6 @@
2
2
  import asyncio
3
3
  import dataclasses
4
4
  import inspect
5
- import textwrap
6
5
  import time
7
6
  import typing
8
7
  import warnings
@@ -44,7 +43,6 @@ from ._utils.async_utils import (
44
43
  warn_if_generator_is_not_consumed,
45
44
  )
46
45
  from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
47
- from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
48
46
  from ._utils.function_utils import (
49
47
  ATTEMPT_TIMEOUT_GRACE_PERIOD,
50
48
  OUTPUTS_TIMEOUT,
@@ -54,6 +52,7 @@ from ._utils.function_utils import (
54
52
  _stream_function_call_data,
55
53
  get_function_type,
56
54
  is_async,
55
+ parse_gpu_config,
57
56
  )
58
57
  from ._utils.grpc_utils import Retry, RetryWarningMessage
59
58
  from ._utils.mount_utils import validate_network_file_systems, validate_volumes, validate_volumes_by_object_id
@@ -67,7 +66,6 @@ from .exception import (
67
66
  NotFoundError,
68
67
  OutputExpiredError,
69
68
  )
70
- from .gpu import GPU_T, parse_gpu_config
71
69
  from .image import _Image
72
70
  from .mount import _get_client_mount, _Mount
73
71
  from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos
@@ -587,7 +585,7 @@ class _FunctionSpec:
587
585
  network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem]
588
586
  volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]]
589
587
  # TODO(irfansharif): Somehow assert that it's the first kind, in sandboxes
590
- gpus: Union[GPU_T, list[GPU_T]]
588
+ gpus: Optional[str | list[str]]
591
589
  cloud: Optional[str]
592
590
  cpu: Optional[Union[float, tuple[float, float]]]
593
591
  memory: Optional[Union[int, tuple[int, int]]]
@@ -666,7 +664,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
666
664
  secrets: Optional[Collection[_Secret]] = None,
667
665
  schedule: Optional[Schedule] = None,
668
666
  is_generator: bool = False,
669
- gpu: Union[GPU_T, list[GPU_T]] = None,
667
+ gpu: Optional[str | list[str]] = None,
670
668
  # TODO: maybe break this out into a separate decorator for notebooks.
671
669
  network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem] = {},
672
670
  volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {},
@@ -703,7 +701,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
703
701
  include_source: bool = True,
704
702
  experimental_options: Optional[dict[str, str]] = None,
705
703
  _experimental_proxy_ip: Optional[str] = None,
706
- _experimental_custom_scaling_factor: Optional[float] = None,
707
704
  restrict_output: bool = False,
708
705
  http_config: Optional[api_pb2.HTTPConfig] = None,
709
706
  ) -> "_Function":
@@ -799,11 +796,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
799
796
  f"for clustered Functions"
800
797
  )
801
798
 
802
- if _experimental_custom_scaling_factor is not None and (
803
- _experimental_custom_scaling_factor < 0 or _experimental_custom_scaling_factor > 1
804
- ):
805
- raise InvalidError("`_experimental_custom_scaling_factor` must be between 0.0 and 1.0 inclusive.")
806
-
807
799
  if not cloud and not is_builder_function:
808
800
  cloud = config.get("default_cloud")
809
801
 
@@ -1050,7 +1042,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1050
1042
  _experimental_group_size=cluster_size or 0, # Experimental: Clustered functions
1051
1043
  _experimental_concurrent_cancellations=True,
1052
1044
  _experimental_proxy_ip=_experimental_proxy_ip,
1053
- _experimental_custom_scaling=_experimental_custom_scaling_factor is not None,
1054
1045
  # --- These are deprecated in favor of autoscaler_settings
1055
1046
  warm_pool_size=min_containers or 0,
1056
1047
  concurrency_limit=max_containers or 0,
@@ -1122,8 +1113,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1122
1113
  function_data.ranked_functions.extend(ranked_functions)
1123
1114
  function_definition = None # function_definition is not used in this case
1124
1115
  else:
1125
- # TODO(irfansharif): Assert on this specific type once we get rid of python 3.9.
1126
- # assert isinstance(gpu, GPU_T) # includes the case where gpu==None case
1127
1116
  function_definition.resources.CopyFrom(
1128
1117
  convert_fn_config_to_resources_config(
1129
1118
  cpu=cpu, memory=memory, gpu=gpu, ephemeral_disk=ephemeral_disk, rdma=rdma
@@ -1170,7 +1159,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1170
1159
  obj._webhook_config = webhook_config # only set locally
1171
1160
 
1172
1161
  # Used to check whether we should rebuild a modal.Image which uses `run_function`.
1173
- gpus: list[GPU_T] = gpu if isinstance(gpu, list) else [gpu]
1162
+ gpus: list[str] = gpu if isinstance(gpu, list) else [gpu] if gpu else []
1174
1163
  obj._build_args = dict( # See get_build_def
1175
1164
  secrets=repr(secrets),
1176
1165
  gpu_config=repr([parse_gpu_config(_gpu) for _gpu in gpus]),
@@ -1358,43 +1347,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1358
1347
  # One idea would be for FunctionUpdateScheduleParams to return the current (coalesced) settings
1359
1348
  # and then we could return them here (would need some ad hoc dataclass, which I don't love)
1360
1349
 
1361
- @live_method
1362
- async def keep_warm(self, warm_pool_size: int) -> None:
1363
- """mdmd:hidden
1364
- Set the warm pool size for the Function.
1365
-
1366
- DEPRECATED: Please adapt your code to use the more general `update_autoscaler` method instead:
1367
-
1368
- ```python notest
1369
- f = modal.Function.from_name("my-app", "function")
1370
-
1371
- # Old pattern (deprecated)
1372
- f.keep_warm(2)
1373
-
1374
- # New pattern
1375
- f.update_autoscaler(min_containers=2)
1376
- ```
1377
- """
1378
- if self._is_method:
1379
- raise InvalidError(
1380
- textwrap.dedent(
1381
- """
1382
- The `.keep_warm()` method can not be used on Modal class *methods*.
1383
-
1384
- Call `.keep_warm()` on the class *instance* instead. All methods of a class are run by the same
1385
- container pool, and this method applies to the size of that container pool.
1386
- """
1387
- )
1388
- )
1389
-
1390
- deprecation_warning(
1391
- (2025, 5, 5),
1392
- "The .keep_warm() method has been deprecated in favor of the more general "
1393
- ".update_autoscaler(min_containers=...) method.",
1394
- show_source=True,
1395
- )
1396
- await self.update_autoscaler(min_containers=warm_pool_size)
1397
-
1398
1350
  @classmethod
1399
1351
  def _from_name(
1400
1352
  cls,
@@ -1444,7 +1396,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1444
1396
  app_name: str,
1445
1397
  name: str,
1446
1398
  *,
1447
- namespace=None, # mdmd:line-hidden
1448
1399
  environment_name: Optional[str] = None,
1449
1400
  client: Optional[_Client] = None,
1450
1401
  ) -> "_Function":
@@ -1459,17 +1410,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1459
1410
  ```
1460
1411
  """
1461
1412
  if "." in name:
1462
- class_name, method_name = name.split(".", 1)
1463
- deprecation_warning(
1464
- (2025, 2, 11),
1465
- "Looking up class methods using Function.from_name will be deprecated"
1466
- " in a future version of Modal.\nUse modal.Cls.from_name instead, e.g.\n\n"
1467
- f'{class_name} = modal.Cls.from_name("{app_name}", "{class_name}")\n'
1468
- f"instance = {class_name}(...)\n"
1469
- f"instance.{method_name}.remote(...)\n",
1413
+ raise InvalidError(
1414
+ f"Invalid Function name: {name!r}. "
1415
+ "If you are trying to look up a class method, use `modal.Cls.from_name` instead."
1470
1416
  )
1471
1417
 
1472
- warn_if_passing_namespace(namespace, "modal.Function.from_name")
1473
1418
  return cls._from_name(
1474
1419
  app_name, name, load_context_overrides=LoadContext(environment_name=environment_name, client=client)
1475
1420
  )
@@ -1587,21 +1532,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1587
1532
  + f"or call it locally: {self._function_name}.local()"
1588
1533
  )
1589
1534
 
1590
- # TODO (live_method on properties is not great, since it could be blocking the event loop from async contexts)
1591
- @property
1592
- @live_method
1593
- async def web_url(self) -> Optional[str]:
1594
- """mdmd:hidden
1595
- Deprecated. Use the `Function.get_web_url()` method instead.
1596
-
1597
- URL of a Function running as a web endpoint.
1598
- """
1599
- deprecation_msg = """The Function.web_url property will be removed in a future version of Modal.
1600
- Use the `Function.get_web_url()` method instead.
1601
- """
1602
- deprecation_warning((2025, 5, 6), deprecation_msg, pending=True)
1603
- return self._web_url
1604
-
1605
1535
  @live_method
1606
1536
  async def get_web_url(self) -> Optional[str]:
1607
1537
  """URL of a Function running as a web endpoint."""
@@ -2110,13 +2040,3 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2110
2040
  except Exception as exc:
2111
2041
  # TODO: kill all running function calls
2112
2042
  raise exc
2113
-
2114
-
2115
- async def _gather(*function_calls: _FunctionCall[T]) -> typing.Sequence[T]:
2116
- """mdmd:hidden
2117
- Deprecated: Please use `modal.FunctionCall.gather()` instead."""
2118
- deprecation_warning(
2119
- (2025, 2, 24),
2120
- "`modal.functions.gather()` is deprecated; please use `modal.FunctionCall.gather()` instead.",
2121
- )
2122
- return await _FunctionCall.gather(*function_calls)
@@ -17,7 +17,7 @@ from modal_proto import api_pb2
17
17
 
18
18
  from ._functions import _Function
19
19
  from ._utils.async_utils import synchronizer
20
- from ._utils.deprecation import deprecation_warning
20
+ from ._utils.deprecation import deprecation_error
21
21
  from ._utils.function_utils import callable_has_non_self_params
22
22
  from .config import config
23
23
  from .exception import InvalidError
@@ -310,7 +310,7 @@ def _method(
310
310
  def wrapper(obj: Union[Callable[..., Any], _PartialFunction]) -> _PartialFunction:
311
311
  flags = _PartialFunctionFlags.CALLABLE_INTERFACE
312
312
 
313
- nonlocal is_generator # TODO(michael): we are likely to deprecate the explicit is_generator param
313
+ nonlocal is_generator
314
314
  if is_generator is None:
315
315
  callable = obj.raw_f if isinstance(obj, _PartialFunction) else obj
316
316
  is_generator = inspect.isgeneratorfunction(callable) or inspect.isasyncgenfunction(callable)
@@ -403,72 +403,16 @@ def _fastapi_endpoint(
403
403
 
404
404
  def _web_endpoint(
405
405
  _warn_parentheses_missing=None, # mdmd:line-hidden
406
- *,
407
- method: str = "GET", # REST method for the created endpoint.
408
- label: Optional[str] = None, # Label for created endpoint. Final subdomain will be <workspace>--<label>.modal.run.
409
- docs: bool = False, # Whether to enable interactive documentation for this endpoint at /docs.
410
- custom_domains: Optional[
411
- Iterable[str]
412
- ] = None, # Create an endpoint using a custom domain fully-qualified domain name (FQDN).
413
- requires_proxy_auth: bool = False, # Require Modal-Key and Modal-Secret HTTP Headers on requests.
414
- ) -> Callable[
415
- [Union[_PartialFunction[P, ReturnType, ReturnType], Callable[P, ReturnType]]],
416
- _PartialFunction[P, ReturnType, ReturnType],
417
- ]:
418
- """Register a basic web endpoint with this application.
419
-
406
+ *args,
407
+ **kwargs,
408
+ ) -> None:
409
+ """mdmd:hidden
420
410
  DEPRECATED: This decorator has been renamed to `@modal.fastapi_endpoint`.
421
-
422
- This is the simple way to create a web endpoint on Modal. The function
423
- behaves as a [FastAPI](https://fastapi.tiangolo.com/) handler and should
424
- return a response object to the caller.
425
-
426
- Endpoints created with `@modal.web_endpoint` are meant to be simple, single
427
- request handlers and automatically have
428
- [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) enabled.
429
- For more flexibility, use `@modal.asgi_app`.
430
-
431
- To learn how to use Modal with popular web frameworks, see the
432
- [guide on web endpoints](https://modal.com/docs/guide/webhooks).
433
411
  """
434
- if isinstance(_warn_parentheses_missing, str):
435
- # Probably passing the method string as a positional argument.
436
- raise InvalidError('Positional arguments are not allowed. Suggestion: `@modal.web_endpoint(method="GET")`.')
437
- elif _warn_parentheses_missing is not None:
438
- raise InvalidError(
439
- "Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@modal.web_endpoint()`."
440
- )
441
-
442
- deprecation_warning(
412
+ deprecation_error(
443
413
  (2025, 3, 5), "The `@modal.web_endpoint` decorator has been renamed to `@modal.fastapi_endpoint`."
444
414
  )
445
415
 
446
- webhook_config = api_pb2.WebhookConfig(
447
- type=api_pb2.WEBHOOK_TYPE_FUNCTION,
448
- method=method,
449
- web_endpoint_docs=docs,
450
- requested_suffix=label or "",
451
- ephemeral_suffix=config.get("dev_suffix"),
452
- async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
453
- custom_domains=_parse_custom_domains(custom_domains),
454
- requires_proxy_auth=requires_proxy_auth,
455
- )
456
-
457
- flags = _PartialFunctionFlags.WEB_INTERFACE
458
- params = _PartialFunctionParams(webhook_config=webhook_config)
459
-
460
- def wrapper(
461
- obj: Union[_PartialFunction[P, ReturnType, ReturnType], Callable[P, ReturnType]],
462
- ) -> _PartialFunction[P, ReturnType, ReturnType]:
463
- if isinstance(obj, _PartialFunction):
464
- pf = obj.stack(flags, params)
465
- else:
466
- pf = _PartialFunction(obj, flags, params)
467
- pf.validate_obj_compatibility("web_endpoint")
468
- return pf
469
-
470
- return wrapper
471
-
472
416
 
473
417
  def _asgi_app(
474
418
  _warn_parentheses_missing=None, # mdmd:line-hidden
modal/_resources.py CHANGED
@@ -3,19 +3,18 @@ from typing import Optional, Union
3
3
 
4
4
  from modal_proto import api_pb2
5
5
 
6
+ from ._utils.function_utils import parse_gpu_config
6
7
  from .exception import InvalidError
7
- from .gpu import GPU_T, parse_gpu_config
8
8
 
9
9
 
10
10
  def convert_fn_config_to_resources_config(
11
11
  *,
12
12
  cpu: Optional[Union[float, tuple[float, float]]],
13
13
  memory: Optional[Union[int, tuple[int, int]]],
14
- gpu: GPU_T,
14
+ gpu: Optional[str],
15
15
  ephemeral_disk: Optional[int],
16
16
  rdma: Optional[bool] = None,
17
17
  ) -> api_pb2.Resources:
18
- gpu_config = parse_gpu_config(gpu)
19
18
  if cpu and isinstance(cpu, tuple):
20
19
  if not cpu[0]:
21
20
  raise InvalidError("CPU request must be a positive number")
@@ -45,7 +44,7 @@ def convert_fn_config_to_resources_config(
45
44
  return api_pb2.Resources(
46
45
  milli_cpu=milli_cpu,
47
46
  milli_cpu_max=milli_cpu_max,
48
- gpu_config=gpu_config,
47
+ gpu_config=parse_gpu_config(gpu),
49
48
  memory_mb=memory_mb,
50
49
  memory_mb_max=memory_mb_max,
51
50
  ephemeral_disk_mb=ephemeral_disk,
@@ -89,55 +89,34 @@ def renamed_parameter(
89
89
  return decorator
90
90
 
91
91
 
92
- def warn_on_renamed_autoscaler_settings(func: Callable[P, R]) -> Callable[P, R]:
93
- name_map = {
94
- "keep_warm": "min_containers",
95
- "concurrency_limit": "max_containers",
96
- "_experimental_buffer_containers": "buffer_containers",
97
- "container_idle_timeout": "scaledown_window",
98
- }
92
+ def handle_deprecated_parameters(func: Callable[P, R]) -> Callable[P, R]:
93
+ """Meta-decorator for modal.Function decorators that errors when deprecated parameters are passed.
94
+
95
+ We use this for a select parameters that might still be suggested by LLMs.
96
+ """
99
97
 
100
98
  @functools.wraps(func)
101
99
  def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
102
- mut_kwargs: dict[str, Any] = locals()["kwargs"] # Avoid referencing kwargs directly due to bug in sigtools
103
-
104
- substitutions = []
105
- old_params_used = name_map.keys() & mut_kwargs.keys()
106
- for old_param, new_param in name_map.items():
107
- if old_param in old_params_used:
108
- new_param = name_map[old_param]
109
- mut_kwargs[new_param] = mut_kwargs.pop(old_param)
110
- substitutions.append(f"- {old_param} -> {new_param}")
111
-
112
- if substitutions:
113
- substitution_string = "\n".join(substitutions)
114
- message = (
115
- "We have renamed several parameters related to autoscaling."
116
- " Please update your code to use the following new names:"
117
- f"\n\n{substitution_string}"
118
- "\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more details."
100
+ _kwargs: dict[str, Any] = locals()["kwargs"] # Avoid referencing kwargs directly due to bug in sigtools
101
+
102
+ # Basic autoscaler parameters
103
+ autoscaler_settings = {
104
+ "keep_warm": "min_containers",
105
+ "concurrency_limit": "max_containers",
106
+ "container_idle_timeout": "scaledown_window",
107
+ }
108
+
109
+ for old_name, new_name in autoscaler_settings.items():
110
+ if old_name in _kwargs:
111
+ deprecation_error((2025, 2, 24), f"The '{old_name}' parameter has been renamed to '{new_name}'.")
112
+
113
+ # Input concurrency
114
+ if "allow_concurrent_inputs" in _kwargs:
115
+ deprecation_error(
116
+ (2025, 4, 9),
117
+ "The 'allow_concurrent_inputs' parameter is deprecated. Use the '@modal.concurrent' decorator instead.",
119
118
  )
120
- deprecation_warning((2025, 2, 24), message, show_source=True)
121
119
 
122
120
  return func(*args, **kwargs)
123
121
 
124
122
  return wrapper
125
-
126
-
127
- def warn_if_passing_namespace(
128
- namespace: Any,
129
- resource_name: str,
130
- ) -> None:
131
- """Issue deprecation warning for namespace parameter if non-None value is passed.
132
-
133
- Args:
134
- namespace: The namespace parameter value (may be None or actual value)
135
- resource_name: Name of the resource type for the warning message
136
- """
137
- if namespace is not None:
138
- deprecation_warning(
139
- (2025, 6, 30),
140
- f"The `namespace` parameter for `{resource_name}` is deprecated and will be"
141
- " removed in a future release. It is no longer needed, so can be removed"
142
- " from your code.",
143
- )
@@ -602,3 +602,20 @@ async def _create_input(
602
602
  ),
603
603
  idx=idx,
604
604
  )
605
+
606
+
607
+ def parse_gpu_config(value: Optional[str]) -> api_pb2.GPUConfig:
608
+ if value is None:
609
+ return api_pb2.GPUConfig()
610
+ elif isinstance(value, str):
611
+ count = 1
612
+ if ":" in value:
613
+ value, count_str = value.split(":", 1)
614
+ try:
615
+ count = int(count_str)
616
+ except ValueError:
617
+ raise InvalidError(f"Invalid GPU count: {count_str}. Value must be an integer.")
618
+ gpu_type = value.upper()
619
+ return api_pb2.GPUConfig(gpu_type=gpu_type, count=count)
620
+ else:
621
+ raise InvalidError(f"Invalid GPU config: {value!r}. Value must be a string or `None`")
modal/app.py CHANGED
@@ -34,7 +34,7 @@ from ._server import _Server, validate_http_server_config
34
34
  from ._utils.async_utils import synchronize_api
35
35
  from ._utils.deprecation import (
36
36
  deprecation_warning,
37
- warn_on_renamed_autoscaler_settings,
37
+ handle_deprecated_parameters,
38
38
  )
39
39
  from ._utils.function_utils import (
40
40
  FunctionInfo,
@@ -50,7 +50,6 @@ from .cls import _Cls, parameter
50
50
  from .config import logger
51
51
  from .exception import ExecutionError, InvalidError
52
52
  from .functions import Function
53
- from .gpu import GPU_T
54
53
  from .image import _Image
55
54
  from .network_file_system import _NetworkFileSystem
56
55
  from .partial_function import PartialFunction
@@ -58,7 +57,6 @@ from .proxy import _Proxy
58
57
  from .retries import Retries
59
58
  from .running_app import RunningApp
60
59
  from .schedule import Schedule
61
- from .scheduler_placement import SchedulerPlacement
62
60
  from .secret import _Secret
63
61
  from .volume import _Volume
64
62
 
@@ -443,7 +441,7 @@ class _App:
443
441
  # - 'rolling' (default): Traffic shifts to the new deployment by starting
444
442
  # new containers gradually. Old containers will continue to process inputs while new
445
443
  # containers start up.
446
- # - 'restart': As part of the deployment, all running containers are terminated. New inputs
444
+ # - 'recreate': As part of the deployment, all running containers are terminated. New inputs
447
445
  # will start new containers.
448
446
  strategy: str = "rolling",
449
447
  ) -> typing_extensions.Self:
@@ -705,7 +703,7 @@ class _App:
705
703
 
706
704
  return wrapped
707
705
 
708
- @warn_on_renamed_autoscaler_settings
706
+ @handle_deprecated_parameters
709
707
  def function(
710
708
  self,
711
709
  _warn_parentheses_missing=None, # mdmd:line-hidden
@@ -714,7 +712,7 @@ class _App:
714
712
  schedule: Optional[Schedule] = None, # An optional Modal Schedule for the function
715
713
  env: Optional[dict[str, Optional[str]]] = None, # Environment variables to set in the container
716
714
  secrets: Optional[Collection[_Secret]] = None, # Secrets to inject into the container as environment variables
717
- gpu: Union[GPU_T, list[GPU_T]] = None, # GPU request; either a single GPU type or a list of types
715
+ gpu: Optional[str | list[str]] = None, # GPU request; either a single GPU type or a list of types
718
716
  serialized: bool = False, # Whether to send the function over using cloudpickle.
719
717
  network_file_systems: dict[
720
718
  Union[str, PurePosixPath], _NetworkFileSystem
@@ -756,17 +754,9 @@ class _App:
756
754
  experimental_options: Optional[dict[str, Any]] = None,
757
755
  # Parameters below here are experimental. Use with caution!
758
756
  _experimental_proxy_ip: Optional[str] = None, # IP address of proxy
759
- _experimental_custom_scaling_factor: Optional[float] = None, # Custom scaling factor
760
757
  _experimental_restrict_output: bool = False, # Don't use pickle for return values
761
758
  # Parameters below here are deprecated. Please update your code as suggested
762
- keep_warm: Optional[int] = None, # Replaced with `min_containers`
763
- concurrency_limit: Optional[int] = None, # Replaced with `max_containers`
764
- container_idle_timeout: Optional[int] = None, # Replaced with `scaledown_window`
765
- allow_concurrent_inputs: Optional[int] = None, # Replaced with the `@modal.concurrent` decorator
766
759
  max_inputs: Optional[int] = None, # Replaced with `single_use_containers`
767
- _experimental_buffer_containers: Optional[int] = None, # Now stable API with `buffer_containers`
768
- _experimental_scheduler_placement: Optional[SchedulerPlacement] = None, # Replaced in favor of
769
- # using `region` and `nonpreemptible`
770
760
  ) -> _FunctionDecoratorType:
771
761
  """Decorator to register a new Modal Function with this App."""
772
762
  if isinstance(_warn_parentheses_missing, _Image):
@@ -778,14 +768,6 @@ class _App:
778
768
  if image is None:
779
769
  image = self._get_default_image()
780
770
 
781
- if allow_concurrent_inputs is not None:
782
- deprecation_warning(
783
- (2025, 4, 9),
784
- "The `allow_concurrent_inputs` parameter is deprecated."
785
- " Please use the `@modal.concurrent` decorator instead."
786
- "\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
787
- )
788
-
789
771
  if max_inputs is not None:
790
772
  if not isinstance(max_inputs, int):
791
773
  raise InvalidError(f"`max_inputs` must be an int, not {type(max_inputs).__name__}")
@@ -800,24 +782,6 @@ class _App:
800
782
  )
801
783
  single_use_containers = max_inputs == 1
802
784
 
803
- if _experimental_scheduler_placement is not None:
804
- deprecation_warning(
805
- (2025, 11, 17),
806
- "The `_experimental_scheduler_placement` parameter is deprecated."
807
- " Please use the `region` and `nonpreemptible` parameters instead.",
808
- )
809
- if region is not None or nonpreemptible:
810
- raise InvalidError(
811
- "Cannot use `_experimental_scheduler_placement` together with "
812
- "`region` or `nonpreemptible` parameters."
813
- )
814
- # Extract regions and lifecycle from scheduler placement
815
- if _experimental_scheduler_placement.proto.regions:
816
- region = list(_experimental_scheduler_placement.proto.regions)
817
- if _experimental_scheduler_placement.proto._lifecycle:
818
- # Convert lifecycle to nonpreemptible: "on-demand" -> True, "spot" -> False
819
- nonpreemptible = _experimental_scheduler_placement.proto._lifecycle == "on-demand"
820
-
821
785
  secrets = secrets or []
822
786
  if env:
823
787
  secrets = [*secrets, _Secret.from_dict(env)]
@@ -836,9 +800,6 @@ class _App:
836
800
  )
837
801
 
838
802
  if isinstance(f, _PartialFunction):
839
- # typically for @function-wrapped @web_endpoint, @asgi_app, or @batched
840
-
841
- # but we don't support @app.function wrapping a method.
842
803
  if is_method_fn(f.raw_f.__qualname__):
843
804
  raise InvalidError(
844
805
  "The `@app.function` decorator cannot be used on class methods. "
@@ -868,7 +829,7 @@ class _App:
868
829
  max_concurrent_inputs = f.params.max_concurrent_inputs
869
830
  target_concurrent_inputs = f.params.target_concurrent_inputs
870
831
  else:
871
- max_concurrent_inputs = allow_concurrent_inputs
832
+ max_concurrent_inputs = None
872
833
  target_concurrent_inputs = None
873
834
  else:
874
835
  if not is_global_object(f.__qualname__) and not serialized:
@@ -905,7 +866,7 @@ class _App:
905
866
  webhook_config = None
906
867
  batch_max_size = None
907
868
  batch_wait_ms = None
908
- max_concurrent_inputs = allow_concurrent_inputs
869
+ max_concurrent_inputs = None
909
870
  target_concurrent_inputs = None
910
871
 
911
872
  cluster_size = None # Experimental: Clustered functions
@@ -964,7 +925,7 @@ class _App:
964
925
  return wrapped
965
926
 
966
927
  @typing_extensions.dataclass_transform(field_specifiers=(parameter,), kw_only_default=True)
967
- @warn_on_renamed_autoscaler_settings
928
+ @handle_deprecated_parameters
968
929
  def cls(
969
930
  self,
970
931
  _warn_parentheses_missing=None, # mdmd:line-hidden
@@ -972,7 +933,7 @@ class _App:
972
933
  image: Optional[_Image] = None, # The image to run as the container for the function
973
934
  env: Optional[dict[str, Optional[str]]] = None, # Environment variables to set in the container
974
935
  secrets: Optional[Collection[_Secret]] = None, # Secrets to inject into the container as environment variables
975
- gpu: Union[GPU_T, list[GPU_T]] = None, # GPU request; either a single GPU type or a list of types
936
+ gpu: Optional[str | list[str]] = None, # GPU request; either a single GPU type or a list of types
976
937
  serialized: bool = False, # Whether to send the function over using cloudpickle.
977
938
  network_file_systems: dict[
978
939
  Union[str, PurePosixPath], _NetworkFileSystem
@@ -1008,17 +969,9 @@ class _App:
1008
969
  experimental_options: Optional[dict[str, Any]] = None,
1009
970
  # Parameters below here are experimental. Use with caution!
1010
971
  _experimental_proxy_ip: Optional[str] = None, # IP address of proxy
1011
- _experimental_custom_scaling_factor: Optional[float] = None, # Custom scaling factor
1012
972
  _experimental_restrict_output: bool = False, # Don't use pickle for return values
1013
973
  # Parameters below here are deprecated. Please update your code as suggested
1014
- keep_warm: Optional[int] = None, # Replaced with `min_containers`
1015
- concurrency_limit: Optional[int] = None, # Replaced with `max_containers`
1016
- container_idle_timeout: Optional[int] = None, # Replaced with `scaledown_window`
1017
- allow_concurrent_inputs: Optional[int] = None, # Replaced with the `@modal.concurrent` decorator
1018
974
  max_inputs: Optional[int] = None, # Replaced with `single_use_containers`
1019
- _experimental_buffer_containers: Optional[int] = None, # Now stable API with `buffer_containers`
1020
- _experimental_scheduler_placement: Optional[SchedulerPlacement] = None, # Replaced in favor of
1021
- # using `region` and `nonpreemptible`
1022
975
  ) -> Callable[[Union[CLS_T, _PartialFunction]], CLS_T]:
1023
976
  """
1024
977
  Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App.
@@ -1026,14 +979,6 @@ class _App:
1026
979
  if _warn_parentheses_missing:
1027
980
  raise InvalidError("Did you forget parentheses? Suggestion: `@app.cls()`.")
1028
981
 
1029
- if allow_concurrent_inputs is not None:
1030
- deprecation_warning(
1031
- (2025, 4, 9),
1032
- "The `allow_concurrent_inputs` parameter is deprecated."
1033
- " Please use the `@modal.concurrent` decorator instead."
1034
- "\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
1035
- )
1036
-
1037
982
  if max_inputs is not None:
1038
983
  if not isinstance(max_inputs, int):
1039
984
  raise InvalidError(f"`max_inputs` must be an int, not {type(max_inputs).__name__}")
@@ -1048,24 +993,6 @@ class _App:
1048
993
  )
1049
994
  single_use_containers = max_inputs == 1
1050
995
 
1051
- if _experimental_scheduler_placement is not None:
1052
- deprecation_warning(
1053
- (2025, 11, 17),
1054
- "The `_experimental_scheduler_placement` parameter is deprecated."
1055
- " Please use the `region` and `nonpreemptible` parameters instead.",
1056
- )
1057
- if region is not None or nonpreemptible:
1058
- raise InvalidError(
1059
- "Cannot use `_experimental_scheduler_placement` together with "
1060
- "`region` or `nonpreemptible` parameters."
1061
- )
1062
- # Extract regions and lifecycle from scheduler placement
1063
- if _experimental_scheduler_placement.proto.regions:
1064
- region = list(_experimental_scheduler_placement.proto.regions)
1065
- if _experimental_scheduler_placement.proto._lifecycle:
1066
- # Convert lifecycle to nonpreemptible: "on-demand" -> True, "spot" -> False
1067
- nonpreemptible = _experimental_scheduler_placement.proto._lifecycle == "on-demand"
1068
-
1069
996
  secrets = secrets or []
1070
997
  if env:
1071
998
  secrets = [*secrets, _Secret.from_dict(env)]
@@ -1086,7 +1013,7 @@ class _App:
1086
1013
  max_concurrent_inputs = wrapped_cls.params.max_concurrent_inputs
1087
1014
  target_concurrent_inputs = wrapped_cls.params.target_concurrent_inputs
1088
1015
  else:
1089
- max_concurrent_inputs = allow_concurrent_inputs
1016
+ max_concurrent_inputs = None
1090
1017
  target_concurrent_inputs = None
1091
1018
 
1092
1019
  if wrapped_cls.flags & _PartialFunctionFlags.CLUSTERED:
@@ -1098,7 +1025,7 @@ class _App:
1098
1025
  rdma = None
1099
1026
  else:
1100
1027
  user_cls = wrapped_cls
1101
- max_concurrent_inputs = allow_concurrent_inputs
1028
+ max_concurrent_inputs = None
1102
1029
  target_concurrent_inputs = None
1103
1030
  cluster_size = None
1104
1031
  rdma = None
@@ -1189,7 +1116,6 @@ class _App:
1189
1116
  include_source=include_source if include_source is not None else local_state.include_source_default,
1190
1117
  experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
1191
1118
  _experimental_proxy_ip=_experimental_proxy_ip,
1192
- _experimental_custom_scaling_factor=_experimental_custom_scaling_factor,
1193
1119
  restrict_output=_experimental_restrict_output,
1194
1120
  )
1195
1121
 
@@ -1215,7 +1141,7 @@ class _App:
1215
1141
  image: Optional[_Image] = None, # The image to run as the container for the server
1216
1142
  env: Optional[dict[str, Optional[str]]] = None, # Environment variables to set in the container
1217
1143
  secrets: Optional[Collection[_Secret]] = None, # Secrets to inject into the container as environment variables
1218
- gpu: Union[GPU_T, list[GPU_T]] = None, # GPU request; either a single GPU type or a list of types
1144
+ gpu: Optional[str | list[str]] = None, # GPU request; either a single GPU type or a list of types
1219
1145
  serialized: bool = False, # Whether to send the server class over using cloudpickle.
1220
1146
  volumes: dict[
1221
1147
  Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]